2014-09-21 59 views
6

要嘗試並簡化這個問題我已經定義了這些箭頭功能:Haskell的記錄語法是否有任何有用的抽象?

splitA :: (Arrow arr) => arr a b -> arr a (b,a) 
splitA ar = ar &&& (arr (\a -> id a)) 

recordArrow 
    :: (Arrow arr) 
    => (d -> r) 
    -> (d -> r -> d) 
    -> (r -> r) 
    -> arr d d 
recordArrow g s f = splitA (arr g >>^ f) >>^ \(r,d) -> s d r 

,然後讓我做這樣的事情:

unarrow :: ((->) b c) -> (b -> c) -- unneeded as pointed out to me in the comments 
unarrow g = g 


data Testdata = Testdata { record1::Int,record2::Int,record3::Int } 

testRecord = unarrow $ 
     recordArrow record1 (\d r -> d { record1 = r }) id 
    >>> recordArrow record2 (\d r -> d { record2 = r }) id 
    >>> recordArrow record3 (\d r -> d { record3 = r }) id 

正如你可以看到這沒有很好地利用的幹。

我希望可能有某種語言擴展可以幫助簡化這個過程。所以,上面的可以簡單地重新寫爲:

testRecord' = unarrow $ 
     recordArrow' record1 id 
    >>> recordArrow' record2 id 
    >>> recordArrow' record3 id 

更新爲清楚起見:要澄清一點。我知道我可以這樣做:

foo d = d { record1 = id (record1 d), record2 = id (record2 d) } 

但是,這忽略了任何執行順序和任何狀態。假設record2的更新函數依賴record1的更新值。或者,我可能想要創建一個不同的箭頭,如下所示:arr d (d,x)然後我想建立一個[x]的列表,其順序取決於記錄的評估順序。

我發現我經常想要執行一些功能,然後更新記錄。我能做到這一點的穿線狀態這樣

g :: d -> r -> d 
foo d = let d' = d { record1 = (g d) (record1 d) } in d' { record2 = (g d') (record2 d') } 

但我覺得箭頭符號是整潔,而且我也可以有[arr d d]和順序把它們結合在一起。另外如果rd是Monads,它會創建整潔的代碼。或者如果他們都是Monad,那麼讓我執行分層綁定而不必使用Monad Transformer。在ST s x的情況下,讓我以有序的方式在狀態s周圍。

我不是想解決一個特定的問題。我只是想找到一個更新記錄語法的抽象方法,而不必明確定義某種「getter」和「setter」。


下面是回答在comments-- 旁註:我已經定義一個函數unarrow轉換功能( - >)箭頭回功能。否則,如果我有someArrow b箭頭arr b c我不能得到價值c。隨着unarrow函數,我可以寫unarrow someArrow b,它工作正常。我覺得我一定在這裏做錯了什麼,因爲我對unarrow的定義只是unarrow g = g

+0

如果給testRecord一個明確的類型簽名,'unarrow'函數可以被刪除。類型推斷有一個問題,它不能發現'testRecord'應該是一個函數類型。 – bmaderbacher 2014-09-21 01:06:48

+0

如果你能澄清一下你想達到的目標,那將會很好。您是否想將函數(本例中爲id)應用於記錄的字段,正如我爲我的答案所假設的那樣? – bmaderbacher 2014-09-21 01:42:37

+0

感謝您提供'unarrow' @bmaderbacher的提示。爲什麼我無法在某些情況下應用箭頭 - 我明確地定義它是類型簽名中的函數似乎很明顯。 – TheCriticalImperitive 2014-09-21 02:05:33

回答

7

您正在尋找的抽象被稱爲鏡頭,Hackage上的lens包可能是目前正在使用的想法中最普遍的實現。使用lens包,你可以定義你recordArrow'

{-# LANGUAGE RankNTypes #-} 

import Control.Arrow 
import Control.Lens 

recordArrow' :: Arrow arr => Lens' d r -> (r -> r) -> arr d d 
recordArrow' field f = arr $ field %~ f 

%~是更新操作,其更新通過給定的鏡頭使用功能的更大的數據結構內的值。

現在的問題是,您不會自動爲您的記錄字段獲取鏡頭,但您可以手動定義它們或使用Template Haskell自動生成它們。例如

{-# LANGUAGE TemplateHaskell #-} 

import Control.Lens 

data Testdata = Testdata { _record1::Int, _record2::Int, _record3::Int } 

makeLenses ''Testdata 

注意,原始記錄存取用下劃線前綴,這樣我們就可以用原來的名字鏡片。

testRecord :: Testdata -> Testdata 
testRecord = 
     recordArrow' record1 id 
    >>> recordArrow' record2 id 
    >>> recordArrow' record3 id 

unarrow功能是不需要的。通過簡單地給出類型簽名,最好將泛型類型強制爲具體類型。

請注意,如果您只是在尋找更好的語法來鏈接記錄操作,並且沒有任何其他用於箭頭的操作,那麼您可能更願意僅將State monad與鏡頭一起使用。例如:

import Control.Monad.State (execState) 

testRecord' :: Testdata -> Testdata 
testRecord' = execState $ do 
    record1 .= 3 
    record2 %= (+5) 
    record3 += 2 
+0

謝謝!這正是我所期待的。在我的搜索中,我偶然發現了異構列表和CTRex(http://www.haskell.org/haskellwiki/CTRex)。但是對於我所希望的超級簡單而言,有很多開銷。 – TheCriticalImperitive 2014-09-21 02:10:00

+0

剛剛看到您更新的問題,並添加了在狀態monad中使用鏡頭的示例。這可能更接近你真正想要的東西。 – shang 2014-09-21 02:34:39