2017-04-05 42 views
11

我是一名Haskell新手,我經常發現自己不得不使用模式匹配來分解數據,只將函數應用於其成員之一,然後重新組裝它。Haskell提供了一種將函數映射到數據成員的方法嗎?

說我有:

data Car = Car { gas :: Int, licensePlate :: String } 

,我希望它一半的天然氣時,驅動器,加油吧,我做的:

mapGas:: (Int -> Int) -> Car -> Car 
mapGas f (Car aGas aLicensePlate) = Car (f aGas) aLicensePlate 

drive:: Car -> Car 
drive = mapGas (flip div 2) 

refuel:: Int -> Car -> Car 
refuel = mapGas . (+) 

有沒有辦法只是做無需定義輔助功能mapGas?因爲當它由許多字段組成時,它不得不爲每個數據成員編寫一個映射函數。我知道有可能爲訪問者的成員之一分配一個值:

runOutOfFuel:: Car -> Car 
runOutOfFuel aCar = aCar { gas = 0 } 

是否有可能將函數映射到訪問器?如果是這樣,怎麼樣?

+2

我想你應該看看一些'鏡頭'教程https://hackage.haskell.org/package/lens – epsilonhalbe

+0

只爲了記錄,沒有鏡頭,我認爲你可以做的最好的是'mapGas f = (\汽車 - >汽車{汽車= f(汽車)})'。這至少避免提及其他領域。 – chi

+0

@chi我通常在這些情況下做的是儘量保持對稱性:'mapGas f =(\ car @ Car {gas = g} - > car {gas = f g})''。時間越長,但你可以在' - >'兩側看到'gas'字段。 – Alec

回答

13

只使用核心庫?但是,隨着廣泛使用的lens包,是的。這裏是什麼樣子,你的情況:

{-# LANGUAGE TemplateHaskell #-} 

import Control.Lens.TH 
import Control.Lens 

data Car = Car { _gas :: Int, _licensePlate :: String } 

makeLenses ''Car 

現在,你可以很容易地獲得/設置/修改嵌套在數據結構的字段。

runOutOfFuel:: Car -> Car 
runOutOfFuel = gas .~ 0 

drive:: Car -> Car 
drive = gas %~ (`div` 2) 

refuel:: Int -> Car -> Car 
refuel c = gas +~ c 

的這裏神奇的是,makeLenses ''Car產生gaslicensePlate功能是相似的(但功能更強大)你mapGas(其實mapGas = (gas %~))。開始使用lens是相當艱鉅的,但我建議您閱讀examples部分。

+0

'Data.Data'和'GHC.Generics'屬於'basic',可以用於這個,但我同意'lens'通常是自然的選擇 –

+0

我也推薦這個'lens'教程(如果你想要的話以更好地理解'lens'):https://artyom.me/lens-over-tea-1 – Shersh

+0

我完全同意「相當令人生畏」。有時候我覺得這有點過分了,超出了實際常見的情況。 – chi

4

沒有語言特性這樣做,但與Haskell中的許多事情一樣,核心語言足夠強大,可以以簡單和優雅的方式實現。

您正在尋找的解決方案是一種值爲的鏡頭。一個鏡頭完全符合你的要求:它允許你採取任何類型的抽象數據並在其一部分上應用一個函數,從而獲得包含修改後的部分的整個數據值。

有一個鏡頭的介紹我很喜歡here。要使用包含的示例,您需要使用lens軟件包。 (Or this one if you're using Stack

+5

我不知道我是否會說鏡頭的存在證明核心語言足夠強大。鏡頭創建是在覈心語言之外明確完成的,使用模板haskell生成一堆代碼是因爲核心語言的功能不足以讓代碼以一般方式寫入一次,然後重新使用。 – amalloy

+1

實際上,您可以在不使用模板的情況下製作鏡片,但人們使用模板簡化模板往往會有點繁瑣。至於功能強大,我的意思是說,很多需要被硬編碼爲其他編程語言的東西可以在Haskell中定義。 –

+0

@amalloy鏡頭創建只能用模板來減少樣板。你可以手寫所有的鏡頭功能,它只會吸。 – Daenyth

相關問題