2014-09-21 51 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