如何简洁地修改一条记录的字段?

问题描述 投票:0回答:3
data Person = Person
  {
    name :: String
  , counter :: Int
  }

incrementPersonCounter :: Person -> Person
incrementPersonCounter p@(Person _ c) = p { counter = c + 1 }

有没有更简洁的方法来执行上述操作?是否有一个我可以使用的函数,在其中指定记录、其字段之一(在本例中为

name
/
counter
)以及应用于返回值的函数?


我在想一些类似的事情:

applyRecord r f f' = r
  { f = f' (f r) }

虽然这行不通,因为:

error: Not in scope: ‘f’
   |
13 |   { f = f' (f r) }
haskell record
3个回答
8
投票

泛化

incrementPersonCounter
的一种方法是抽象修改函数:

modifyPersonCounter :: (Int -> Int) -> Person -> Person
modifyPersonCounter f p = (\c -> p { counter = c}) $ f (counter p)

事实上,一个常见的模式是对我们想要在该领域执行的效果进行抽象:

counterLens :: forall f. Functor f => (Int -> f Int) -> (Person -> f Person)
counterLens f p = (\c -> p { counter = c }) <$> f (counter p)

例如,我们可能想从控制台或数据库读取计数器的增加(都是

IO
效果)。

我们可以给函数类型一个同义词,给定一种(可能有效的)更改字段的方法,返回一个转换整个记录的函数:

type Lens' a b = forall f. Functor f => (b -> f b) -> (a -> f a)

要纯粹地修改记录,现在我们需要一个可以调用的辅助函数over

,该函数只需要定义一次:

over :: Lens' a b -> (b -> b) -> a -> a over l f p = runIdentity $ l (Identity . f) p

例如:

*Main> over counterLens (+1) (Person "foo" 40) Person {name = "foo", counter = 41}


我们已经抽象出了修改函数及其可能的效果,但是我们仍然需要为每个字段定义这些“镜头”,这很烦人。在实践中,人们使用

Template Haskell 自动定义它们并避免样板。

但是如果我们想要一个

single 函数来指定字段名称怎么办?遗憾的是,这更加复杂。您需要一种将类型级字符串作为参数传递的方法,以及一个对字段名称、记录类型和字段类型之间的关系进行编码的多参数类型类。有一些 一些包 可以做到这一点(再次,使用 Template Haskell 帮助样板),但据我所知,它们并没有被广泛使用。

镜头的主要库称为

lens,还有 microlens,这是一种依赖性较小的替代方案。它们是可互操作的:使用一个库定义的镜头可以与另一个库配合使用。


5
投票
使用

lens你可以这样写:

incrementPersonCounter :: Person -> Person incrementPersonCounter = counter +~ 1

示例:

λ> incrementPersonCounter $ Person "foo" 42 Person {_name = "foo", _counter = 43}


完整代码:

{-# LANGUAGE TemplateHaskell #-} module Lib where import Control.Lens data Person = Person { _name :: String , _counter :: Int } deriving (Show, Eq) makeLenses ''Person incrementPersonCounter :: Person -> Person incrementPersonCounter = counter +~ 1
    

0
投票
自 GHC 9.2 以来,有两种新的语言扩展支持此功能:

https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_record_dot.html https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_record_update.html

© www.soinside.com 2019 - 2024. All rights reserved.