2016-09-17 45 views
3

作爲示例,我有一輛自行車,前輪&後輪,它們都只有一個Int來表示直徑。Haskell:根據變量替換記錄中的字段

type Wheel = Int 
data Bike = Bike { frontwheel :: Wheel, rearwheel :: Wheel } 
    deriving (Show) 

mybike = Bike 24 26 

現在我想更換車輪,因爲我不喜歡他們是不同的大小:

replaceFrontWheel :: Bike -> Wheel -> Bike 
replaceFrontWheel bike wheel = bike { frontwheel = wheel } 

repairedbike = replaceFrontWheel mybike 26 

這一工程!

但是,如果我想要一個功能,可以取代前面的後輪?這兩個輪子,是輪(INT)之後輸入的所有的,爲什麼不能用一個單一的函數,它接受的字段作爲參數以及做:

replaceWheel bike position wheel = bike { position = wheel } 

repairedbike = replaceWheel mybike frontwheel 26 

我不明白爲什麼不工作。 position不被解釋爲具有值frontwheel,而是作爲Bike的(不存在的)字段position

Haskell模擬的(JS)mybike[position] = 26或(PHP)$mybike->$position = 26

是否有可能沒有任何外部模塊的優雅方式?

否則,是否有可能使用鏡頭?

+5

這幾乎是鏡頭的一切。 – melpomene

回答

6

是的,lens es正是你所需要的。

import Control.Lens 
import Control.Lens.TH 

data Bike = Bike { _frontwheel, _rearwheel :: Wheel } 
deriving (Show) 
makeLenses ''Bike 

replaceWheel :: Bike -> Lens' Bike Wheel -> Wheel -> Bike 
replaceWheel bike position wheel = bike & position .~ wheel 

的情況下使用,就像你想要的東西:

repairedbike = replaceWheel mybike frontwheel 26 

可以削弱簽名位:

replaceWheel :: Bike -> Setter' Bike Wheel -> Wheel -> Bike 

基本上是說

replaceWheel :: Bike 
      -> ((Wheel->Identity Wheel) -> (Bike->Identity Bike)) 
      -> Wheel 
      -> Bike 
的只是一種奇特的方式

因爲Identity僅僅是一個類型級同構,你不妨忽略它,從而結束你了

replaceWheel :: Bike -> ((Wheel->Wheel) -> Bike->Bike) -> Wheel -> Bike 
replaceWheel bike position wheel = bike & position (const wheel) 
           -- = position (const wheel) bike 

可以因此被稱爲:

data Bike = Bike { _frontwheel, _rearwheel :: Wheel } -- no lenses 

frontWheel :: (Wheel -> Wheel) -> Bike -> Bike 
frontWheel f (Bike fw rw) = Bike (f fw) rw 

repairedbike = replaceWheel mybike frontwheel 26 

所以,你的確不嚴格地說爲此需要任何圖書館!

,它的preferrable使用正確的鏡頭,而不是這樣的ad-hoc近似的原因包括:

  • 比較一般。 A Lens'可以同時用於設置,獲取(和遍歷)值。這隻能在沒有lens使用的基礎Rank2多態性的情況下難以表達。
  • 更簡潔。上面的類型有很多冗餘;鏡頭爲您提供這些訪問器的簡短同義詞。
  • 更安全。功能(Wheel -> Wheel) -> Bike -> Bike可以做所有類型的垃圾; lens需要基本上保證鏡頭實際上像記錄存取器一樣工作的鏡頭法則,僅此而已。
  • 快。鏡頭庫中的組合器是用性能來編寫的(即支持流融合的內聯,在狀態monad中省略複製等)。

順便說一句,對於函數「修改的東西」這是在Haskell常規把論點最後修改:

replaceWheel :: Setter' Bike Wheel -> Wheel -> Bike -> Bike 
replaceWheel position wheel = position .~ wheel 

...或者,甚至更短,

replaceWheel = (.~) 
+0

感謝您的全面解釋!運行'cabal install鏡頭'並將'{ - #LANGUAGE TemplateHaskell# - }'添加到我的文件頂部後,它可以很好地工作。您還回答了我的另一個問題:如何使用函數轉換字段。結果'(%〜)'適合我的需求! – Fx32