简体   繁体   English

使用 Haskell 中的 function 更新记录中的字段

[英]Updating a field in a record with a function in Haskell

I would like to have a function that can update a given field which I call "updateField".我想要一个 function 可以更新我称之为“updateField”的给定字段。

But there is an error message " error: Not in scope: `f ' ".但是有一条错误消息“错误:不在scope中:`f'”。 How can this be solved?如何解决?

data Record = Record
  { a :: Int
  , b :: Int
  } deriving Show

initRecord = Record
  { a = 1
  , b = 2
  }

updateField :: Record -> (Record -> Int) -> Int -> Record
updateField rec f n = rec { f = n }

You are currently passing a function of type Record -> Int as the parameter of updateField .您当前正在传递Record -> Int类型的 function 作为updateField的参数。 This could be any function, not just a field of Record , so even if passing first-class fields were allowed, what should happen if you call updateField with a function like \ _rec -> 2 + 2 instead of a or b ?这可以是任何function,而不仅仅是Record的字段,因此即使允许传递一流的字段,如果您使用 function (如\ _rec -> 2 + 2而不是ab )调用updateField会发生什么?

A simple alternative in this case is to pass a setter function instead:在这种情况下,一个简单的替代方法是通过设置器function 代替:

updateField
  :: Record
  -> (Record -> Int -> Record)
  -> Int
  -> Record
updateField rec setField n = setField rec n

setA, setB :: Record -> Int -> Record
setA rec n = rec { a = n }
setB rec n = rec { b = n }

Usage:用法:

updateField (updateField initRecord setA 10) setB 20

If you also need to get the field, pass both:如果您还需要获取该字段,请同时传递:

modifyField
  :: Record
  -> (Record -> Int)
  -> (Record -> Int -> Record)
  -> (Int -> Int)
  -> Record
modifyField rec getField setField f
  = setField rec (f (getField rec))
modifyField
  (modifyField initRecord a setA (+ 1))
  b
  setB
  (* 2)

Of course, this is somewhat error-prone, because you must pass both a and setA at the call site, or b and setB &c., and they must match.当然,这有点容易出错,因为您必须在调用站点同时传递asetA ,或者bsetB &c.,并且它们必须匹配。 The standard approach to this is to bundle the getter and setter together into a first-class accessor called a lens (or more generally an optic ):解决这个问题的标准方法是将 getter 和 setter 捆绑到一个称为lens (或更一般地optic )的一流访问器中:

-- Required to pass a ‘Lens’ as an argument,
-- since it’s polymorphic.
{-# LANGUAGE RankNTypes #-}

import Control.Lens (Lens', set)

data Record = Record
  { a :: Int
  , b :: Int
  } deriving Show

-- ‘fieldA’ and ‘fieldB’ are first-class accessors of a
-- field of type ‘Int’ within a structure of type ‘Record’.
fieldA, fieldB :: Lens' Record Int

-- Equivalent to this function type:
--   :: (Functor f) => (Int -> f Int) -> Record -> f Record

-- Basically: extract the value, run a function on it,
-- and reconstitute the result.
fieldA f r = fmap (\ a' -> r { a = a' }) (f (a r))
fieldB f r = fmap (\ b' -> r { b = b' }) (f (b r))

initRecord :: Record
initRecord = Record
  { a = 1
  , b = 2
  }

updateField :: Record -> Lens' Record Int -> Int -> Record
updateField rec f n = set f n rec

You use set to set the field, view to get it, and over to apply a function to it.您使用set设置字段, view以获取它,然后将over应用到它。

view fieldA (updateField initRecord fieldA 10)
-- =
a (initRecord { a = 10 })
-- =
10

Since lenses are completely mechanical, they are often derived automatically using Template Haskell, conventionally by prefixing the actual fields with _ and deriving lenses without that prefix:由于镜头是完全机械的,它们通常使用模板 Haskell 自动派生,通常通过在实际字段前加上_前缀并派生不带前缀的镜头:

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens.TH (makeLenses)

data Record = Record
  { _a :: Int
  , _b :: Int
  } deriving Show

makeLenses ''Record

-- Generated code:
--
-- a, b :: Lens' Record Int
-- a f r = fmap (\ a' -> r { _a = a' }) (f (_a r))
-- b f r = fmap (\ b' -> r { _b = b' }) (f (_b r))
view a (updateField initRecord a 10)
-- =
_a (initRecord { _a = 10 })
-- =
10

In fact, updateField and modifyField are now redundant, since you can just use set and over from lens :实际上, updateFieldmodifyField现在是多余的,因为您可以从lens使用setover

view a $ over b (* 10) $ set a 5 initRecord

Optics are probably overkill in this scenario, but they have many other advantages in larger examples, since they let you do all kinds of accesses and traversals of complex nested data structures without having to manually take apart and put back together records, so they're well worth adding to your Haskell repertoire at some point.在这种情况下,光学可能有点矫枉过正,但在更大的示例中它们还有许多其他优势,因为它们允许您对复杂的嵌套数据结构进行各种访问和遍历,而无需手动拆开记录并将其重新组合在一起,因此它们是非常值得在某个时候添加到您的 Haskell 曲目中。 The lens package defines many operator symbols for every conceivable use case, but even using the basic named functions like view , set , over , at , and so on will get you a long way. lens package 为每个可能的用例定义了许多运算符符号,但即使使用基本的命名函数,如viewsetoverat等,也会让你走得很远。 For a smaller dependency, there's also microlens as a good alternative.对于较小的依赖性,还有microlens作为一个很好的选择。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM