Haskell的记录语法是否有任何有用的抽象?

The*_*ive 6 haskell arrows record

为了尝试简化这个问题,我已经定义了这些箭头函数:

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
Run Code Online (Sandbox Code Playgroud)

然后让我做这样的事情:

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
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,这并没有很好地利用DRY.

我希望可能有某种语言扩展可以帮助简化这个过程.因此,上述内容可以简单地重写为:

testRecord' = unarrow $
       recordArrow' record1 id
   >>> recordArrow' record2 id
   >>> recordArrow' record3 id
Run Code Online (Sandbox Code Playgroud)

为清楚起见更新:澄清一点.我知道我可以这样做:

foo d = d { record1 = id (record1 d), record2 = id (record2 d) }
Run Code Online (Sandbox Code Playgroud)

但这会忽略任何执行顺序和任何状态.假设更新函数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') }
Run Code Online (Sandbox Code Playgroud)

但我认为箭头符号更整洁,我也可以[arr d d]按顺序将它们链接在一起.此外,如果r或是dMonads,它会创建更整洁的代码.或者,如果它们都是Monads,那么我就可以执行分层绑定,而无需使用Monad Transformer.在ST s x它的情况下,让我s以有序的方式绕过状态.

我不是想解决一个特定的问题.我只是想找到一种更新记录语法的抽象方法,而不必明确定义某种"getter"和"setter".


下面的评论中回答了这一点 - 旁注:我必须定义一个函数,unarrow用于将函数( - >)箭头转换回函数.否则,如果我有someArrow b箭头,arr b c我就无法获得价值c.使用unarrow我可以编写的功能unarrow someArrow b,它工作正常.我觉得我必须在这里做错事,因为我对unarrow的定义很简单unarrow g = g.

sha*_*ang 7

你正在寻找的抽象被称为镜头,lensHackage上的软件包可能是目前使用的思想中最广泛的实现.使用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
Run Code Online (Sandbox Code Playgroud)

%~是一个更新运算符,它使用给定镜头的函数更新较大数据结构内的值.

现在的问题是你没有自动获取记录字段的镜头,但你可以手动定义它们或使用Template Haskell自动生成它们.例如

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

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

makeLenses ''Testdata
Run Code Online (Sandbox Code Playgroud)

请注意,原始记录访问器以下划线为前缀,以便我们可以使用镜头的原始名称.

testRecord :: Testdata -> Testdata
testRecord =
       recordArrow' record1 id
   >>> recordArrow' record2 id
   >>> recordArrow' record3 id
Run Code Online (Sandbox Code Playgroud)

unarrow功能不需要.通过简单地给出类型签名,最好将泛型类型强制为具体类型.

请注意,如果您只是寻找更好的链接记录操作的语法,并且没有任何其他箭头用途,您可能更喜欢使用State带镜头的monad.例如:

import Control.Monad.State (execState)

testRecord' :: Testdata -> Testdata
testRecord' = execState $ do
    record1 .= 3
    record2 %= (+5)
    record3 += 2
Run Code Online (Sandbox Code Playgroud)