使用 Haskell 中的函数更新记录中的字段
Updating a field in a record with a function in Haskell
我想要一个可以更新给定字段的函数,我称之为“updateField”。
但是出现错误信息“error: Not in scope: `f'”。如何解决?
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 }
您当前正在传递类型为 Record -> Int
的函数作为 updateField
的参数。这可能是 any 函数,而不仅仅是 Record
的字段,所以即使允许传递 first-class 字段,如果调用 updateField
的函数类似于 \ _rec -> 2 + 2
而不是 a
或 b
?
在这种情况下,一个简单的替代方法是传递一个 setter 函数:
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 }
用法:
updateField (updateField initRecord setA 10) setB 20
如果您还需要获取该字段,请同时传递:
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)
当然,这有点容易出错,因为你必须在调用站点同时传递 a
和 setA
,或者 b
和 setB
&c。 ,并且它们必须匹配。对此的标准方法是将 getter 和 setter 捆绑在一起成为第一个 class 访问器,称为 lens (或更一般地 光学):
-- 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
您使用 set
设置字段,view
获取字段,over
对其应用函数。
view fieldA (updateField initRecord fieldA 10)
-- =
a (initRecord { a = 10 })
-- =
10
由于镜头是完全机械的,它们通常使用模板 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
事实上,updateField
和 modifyField
现在是多余的,因为您可以使用 set
和 over
来自 lens
:
view a $ over b (* 10) $ set a 5 initRecord
Optics 在这种情况下 可能 矫枉过正,但在更大的例子中它们还有许多其他优势,因为它们允许您对复杂的嵌套数据结构进行各种访问和遍历,而无需必须手动拆开和放回记录,因此它们非常值得在某些时候添加到您的 Haskell 曲目中。 lens
包为每个可能的用例定义了许多运算符符号,但即使使用基本的命名函数,如 view
、set
、over
、at
和等等会让你走很长的路。对于较小的依赖项,microlens
也是一个不错的选择。
我想要一个可以更新给定字段的函数,我称之为“updateField”。
但是出现错误信息“error: Not in scope: `f'”。如何解决?
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 }
您当前正在传递类型为 Record -> Int
的函数作为 updateField
的参数。这可能是 any 函数,而不仅仅是 Record
的字段,所以即使允许传递 first-class 字段,如果调用 updateField
的函数类似于 \ _rec -> 2 + 2
而不是 a
或 b
?
在这种情况下,一个简单的替代方法是传递一个 setter 函数:
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 }
用法:
updateField (updateField initRecord setA 10) setB 20
如果您还需要获取该字段,请同时传递:
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)
当然,这有点容易出错,因为你必须在调用站点同时传递 a
和 setA
,或者 b
和 setB
&c。 ,并且它们必须匹配。对此的标准方法是将 getter 和 setter 捆绑在一起成为第一个 class 访问器,称为 lens (或更一般地 光学):
-- 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
您使用 set
设置字段,view
获取字段,over
对其应用函数。
view fieldA (updateField initRecord fieldA 10)
-- =
a (initRecord { a = 10 })
-- =
10
由于镜头是完全机械的,它们通常使用模板 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
事实上,updateField
和 modifyField
现在是多余的,因为您可以使用 set
和 over
来自 lens
:
view a $ over b (* 10) $ set a 5 initRecord
Optics 在这种情况下 可能 矫枉过正,但在更大的例子中它们还有许多其他优势,因为它们允许您对复杂的嵌套数据结构进行各种访问和遍历,而无需必须手动拆开和放回记录,因此它们非常值得在某些时候添加到您的 Haskell 曲目中。 lens
包为每个可能的用例定义了许多运算符符号,但即使使用基本的命名函数,如 view
、set
、over
、at
和等等会让你走很长的路。对于较小的依赖项,microlens
也是一个不错的选择。