[英]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
而不是a
或b
)调用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.当然,这有点容易出错,因为您必须在调用站点同时传递a
和setA
,或者b
和setB
&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
:实际上, updateField
和modifyField
现在是多余的,因为您可以从lens
使用set
和over
:
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 为每个可能的用例定义了许多运算符符号,但即使使用基本的命名函数,如view
、 set
、 over
、 at
等,也会让你走得很远。 For a smaller dependency, there's also microlens
as a good alternative.对于较小的依赖性,还有microlens
作为一个很好的选择。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.