Haskell的记录语法是否有任何有用的抽象? [英] Are there any useful abstractions for Haskell's record syntax?

查看:160
本文介绍了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)→> sdr

然后让我做这样的事情:

  unarrow ::(( - >)bc) - > (b  - > c) - 在评论中指出的不需要的
unarrow g = g


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

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

正如您所见这并不能很好地使用DRY。

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

  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'{record2 =(g d')(record2 d')} 
foo d = let d'= d {record1 =(gd)(record1 d)} c>

但我认为箭头符号很整洁,而且我也可以有 [arr dd] 并按顺序将它们链接在一起。如果 r d 是Monads,它会创建整洁的代码。或者如果他们都是Monad,那么让我执行分层绑定而不必使用Monad Transformer。在 ST sx 的情况下,让我以有序的方式对状态 s 进行线程化。



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




下面是在评论中回答 -
旁注:我必须定义一个函数 unarrow 来转换一个函数( - > )箭头回到功能。否则,如果我有 someArrow b 作为箭头 arr bc 我无法获得值 ç。使用 unarrow 函数,我可以写 unarrow someArrow b ,它工作正常。我觉得我必须在这里做错事,因为我对unarrow的定义只是 unarrow g = g

解决方案

您正在寻找的抽象被称为镜头,而 <$ c Hackage上的$ c> lens 软件包可能是目前正在使用的最广泛的实现。使用镜头包,您可以将 recordArrow'定义为

  { - #LANGUAGE RankNTypes# - } 

导入Control.Arrow
导入Control.Lens

recordArrow': :Arrow arr =>镜头'd r - > (r→r)→> arr dd
recordArrow'field f = arr $ field%〜f

%〜 是一个更新运算符,它使用函数通过给定的镜头更新更大数据结构中的值。

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

  { - #LANGUAGE TemplateHaskell# - } 

导入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。例如:

$ p $ import Control.Monad.State(execState)

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


To try and simplify this problem I have defined these arrow functions:

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

Which then let's me do something like this:

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

As you can see this doesn't make very good use of DRY.

I'm hoping there might be some sort of language extension that could help simplify this process. So that the above could simply be re-written as:

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

Updated for clarity: To clarify a little bit. I'm aware that I could do something like this:

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

But this ignores any execution order and any state. Suppose the update function for record2 relies on the updated value for record1. Or alternatively, I may want to create a different arrow that looks like this: arr d (d,x) and then I want to build a list of [x] the order of which depends on the evaluation order of the records.

What I've found is that I frequently want to execute some functionality and afterwards update a record. I can do that by threading the state like this

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

But I think arrow notation is neater, and I could also have [arr d d] and chain them together in a sequence. Plus if r or d are Monads it creates neater code. Or if they're both Monads, it let's me perform layered binds without having to use a Monad Transformer. In the case of ST s x it let's me thread the state s around in an ordered way.

I'm not trying to solve one particular problem. I'm just trying to find an abstracted method of updating a record syntax without having to explicitly define some sort of "getter" and "setter".


Below was answered in the comments-- Sidenote: I've had to define a function unarrow for converting a function (->) arrow back to a function. Otherwise if I have someArrow b for an arrow arr b c I can't get to value c. With the unarrow function I can write unarrow someArrow b and it works fine. I feel like I must be doing something wrong here because my definition for unarrow is simply unarrow g = g.

解决方案

The abstraction you are looking for is called a lens and the lens package on Hackage is probably the most widespread implementation of the idea currently in use. Using the lens package you can define your recordArrow' as

{-# 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

The %~ is an update operator which updates a values inside a larger data structures using a function via the given lens.

Now the problem is that you don't automatically get lenses for your record fields, but you can either manually define them or generate them automatically using Template Haskell. For example

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

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

makeLenses ''Testdata

Note that the original record accessors are prefixed with underscores so that we can use the original names for the lenses.

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

The unarrow function is not needed. It's better to force a generic type to a concrete type by simple giving a type signature.

Note that if you are just looking for a nicer syntax to chain record operations and don't have any other use for arrows, you might prefer just to use the State monad with lenses. For example:

import Control.Monad.State (execState)

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

这篇关于Haskell的记录语法是否有任何有用的抽象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆