用于获取或设置由运行时参数确定的记录字段的镜头
A lens for getting or setting a record field determined by a runtime argument
我有这些类型(以及更多):
data Player = PlayerOne | PlayerTwo deriving (Eq, Show, Read, Enum, Bounded)
data Point = Love | Fifteen | Thirty deriving (Eq, Show, Read, Enum, Bounded)
data PointsData =
PointsData { pointsToPlayerOne :: Point, pointsToPlayerTwo :: Point }
deriving (Eq, Show, Read)
我正在做 Tennis kata,作为实现的一部分,我想使用一些函数来获取或设置任意玩家的点数,这些点数仅在运行时已知。
形式上,我需要这样的函数:
pointFor :: PointsData -> Player -> Point
pointFor pd PlayerOne = pointsToPlayerOne pd
pointFor pd PlayerTwo = pointsToPlayerTwo pd
pointTo :: PointsData -> Player -> Point -> PointsData
pointTo pd PlayerOne p = pd { pointsToPlayerOne = p }
pointTo pd PlayerTwo p = pd { pointsToPlayerTwo = p }
如上所示,我的问题不是我无法实现这些功能。
但是,在我看来,它们确实 像镜头一样 ,所以我想知道我是否可以通过 lens
库获得该功能?
大多数镜头教程展示了如何获取或设置更大数据结构的特定命名部分。这似乎不太适合我在这里尝试做的事情;相反,我正在尝试获取或设置在运行时确定的子部分。
您可以创建一个函数,在给定 Player
的情况下生成 Lens
,如下所示:
playerPoints :: Player -> Lens' PointsData Point
playerPoints PlayerOne = field @"pointsToPlayerOne"
playerPoints PlayerTwo = field @"pointsToPlayerTwo"
(这是使用 field
from generic-lens
)
用法是这样的:
pts :: PointsData
pl1 = pts ^. playerPoints PlayerOne
pl2 = pts ^. playerPoints PlayerTwo
newPts = pts & playerPoints PlayerOne .~ 42
P.S。或者您是否正在寻找通过将字段名称与 Player
构造函数名称匹配来选择 PointsData
的字段?这也可以通过 Generic
,但似乎不值得这么麻烦。
对有点抽象的类型类的探索。您的 PointsData
与 Player
类型有着特殊的关系。它有点像 Map Player Point
,具有特殊性,对于 Player
的 每个 可能的值,都有 always 对应Point
。在某种程度上,PointsData
就像 "reified function" Player -> Point
.
如果我们在 Points
类型上使 PointsData
多态,它将适合 Representable
类型类。我们会说 PointsData
是 "represented" by Player
.
Representable
通常用作表格数据的接口,例如在 grids 包中。
所以一个可能的解决方案是将 PointsData
变成实际的 Map
,但将实现隐藏在智能构造函数后面,该构造函数采用 Player -> Point
函数为所有可能的初始化它键(它将对应于 Representable
的 tabulate
方法)。
用户不能从地图中删除键。但是我们可以搭载 Map
的 Ixed
实例来提供遍历。
import Control.Lens
import Data.Map.Strict -- from "containers"
newtype PointsData = PointsData { getPoints :: Map Player Point }
init :: (Player -> Point) -> PointsData
init f = PointsData (Data.Map.Strict.fromList ((\p -> (p, f p)) <$> [minBound..maxBound]))
playerPoints :: Player -> Lens' PointsData Point
playerPoints pl = Control.Lens.singular (iso getPoints PointsData . ix pl)
基于 和 duplode 的评论,我最终使用了 lens 的 makeLenses
和编写一个 returns 合适镜头的函数:
data PointsData =
PointsData { _pointsToPlayerOne :: Point, _pointsToPlayerTwo :: Point }
deriving (Eq, Show, Read)
makeLenses ''PointsData
playerPoint :: Player -> Lens' PointsData Point
playerPoint PlayerOne = pointsToPlayerOne
playerPoint PlayerTwo = pointsToPlayerTwo
它可以像这个更大函数的片段一样使用:
score :: Score -> Player -> Score
-- ..
score (Points pd) winner = Points $ pd & playerPoint winner %~ succ
-- ..
我有这些类型(以及更多):
data Player = PlayerOne | PlayerTwo deriving (Eq, Show, Read, Enum, Bounded)
data Point = Love | Fifteen | Thirty deriving (Eq, Show, Read, Enum, Bounded)
data PointsData =
PointsData { pointsToPlayerOne :: Point, pointsToPlayerTwo :: Point }
deriving (Eq, Show, Read)
我正在做 Tennis kata,作为实现的一部分,我想使用一些函数来获取或设置任意玩家的点数,这些点数仅在运行时已知。
形式上,我需要这样的函数:
pointFor :: PointsData -> Player -> Point
pointFor pd PlayerOne = pointsToPlayerOne pd
pointFor pd PlayerTwo = pointsToPlayerTwo pd
pointTo :: PointsData -> Player -> Point -> PointsData
pointTo pd PlayerOne p = pd { pointsToPlayerOne = p }
pointTo pd PlayerTwo p = pd { pointsToPlayerTwo = p }
如上所示,我的问题不是我无法实现这些功能。
但是,在我看来,它们确实 像镜头一样 ,所以我想知道我是否可以通过 lens
库获得该功能?
大多数镜头教程展示了如何获取或设置更大数据结构的特定命名部分。这似乎不太适合我在这里尝试做的事情;相反,我正在尝试获取或设置在运行时确定的子部分。
您可以创建一个函数,在给定 Player
的情况下生成 Lens
,如下所示:
playerPoints :: Player -> Lens' PointsData Point
playerPoints PlayerOne = field @"pointsToPlayerOne"
playerPoints PlayerTwo = field @"pointsToPlayerTwo"
(这是使用 field
from generic-lens
)
用法是这样的:
pts :: PointsData
pl1 = pts ^. playerPoints PlayerOne
pl2 = pts ^. playerPoints PlayerTwo
newPts = pts & playerPoints PlayerOne .~ 42
P.S。或者您是否正在寻找通过将字段名称与 Player
构造函数名称匹配来选择 PointsData
的字段?这也可以通过 Generic
,但似乎不值得这么麻烦。
对有点抽象的类型类的探索。您的 PointsData
与 Player
类型有着特殊的关系。它有点像 Map Player Point
,具有特殊性,对于 Player
的 每个 可能的值,都有 always 对应Point
。在某种程度上,PointsData
就像 "reified function" Player -> Point
.
如果我们在 Points
类型上使 PointsData
多态,它将适合 Representable
类型类。我们会说 PointsData
是 "represented" by Player
.
Representable
通常用作表格数据的接口,例如在 grids 包中。
所以一个可能的解决方案是将 PointsData
变成实际的 Map
,但将实现隐藏在智能构造函数后面,该构造函数采用 Player -> Point
函数为所有可能的初始化它键(它将对应于 Representable
的 tabulate
方法)。
用户不能从地图中删除键。但是我们可以搭载 Map
的 Ixed
实例来提供遍历。
import Control.Lens
import Data.Map.Strict -- from "containers"
newtype PointsData = PointsData { getPoints :: Map Player Point }
init :: (Player -> Point) -> PointsData
init f = PointsData (Data.Map.Strict.fromList ((\p -> (p, f p)) <$> [minBound..maxBound]))
playerPoints :: Player -> Lens' PointsData Point
playerPoints pl = Control.Lens.singular (iso getPoints PointsData . ix pl)
基于 makeLenses
和编写一个 returns 合适镜头的函数:
data PointsData =
PointsData { _pointsToPlayerOne :: Point, _pointsToPlayerTwo :: Point }
deriving (Eq, Show, Read)
makeLenses ''PointsData
playerPoint :: Player -> Lens' PointsData Point
playerPoint PlayerOne = pointsToPlayerOne
playerPoint PlayerTwo = pointsToPlayerTwo
它可以像这个更大函数的片段一样使用:
score :: Score -> Player -> Score
-- ..
score (Points pd) winner = Points $ pd & playerPoint winner %~ succ
-- ..