我有这些类型(还有更多):
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
库获得该功能?
大多数镜头教程都展示了如何获取或设置更大数据结构中特定的命名部分。这似乎不太适合我在这里要做的事情;相反,我试图获取或设置在运行时确定的子部件。
答案 0 :(得分:4)
您可以创建一个在给定Lens
的情况下产生Player
的函数,如下所示:
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。或者,您是否正在通过将字段名称与PointsData
构造函数名称匹配来选择Player
的字段?也可以通过Generic
来做到这一点,但似乎并不值得。
答案 1 :(得分:4)
游览有点抽象的类。您的PointsData
与Player
类型有特殊关系。有点像Map Player Point
,其特殊之处在于,对于每个可能的值Player
,总是有一个 对应的Point
。从某种意义上说,PointsData
就像一个“归一化函数” Player -> Point
。
如果我们使PointsData
的类型上的Points
多态,它将适合Representable
类型类。我们可以说PointsData
由Player
“代表”。
Representable
通常用作表格数据的接口,例如在grids包中。
因此,一种可能的解决方案是将PointsData
转换为实际的Map
,但将实现隐藏在使用Player -> Point
函数为所有可能的键进行初始化的智能构造函数的后面(它将与Representable
的{{3}}方法相对应。)
用户应该不能从地图上删除键。但是我们可以搭载Map
的{{3}}实例来提供遍历。
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)
答案 2 :(得分:0)
基于answer from Fyodor Soikin和 duplode 的评论,我最终使用了 lens 的makeLenses
并编写了一个返回适当镜头的函数:
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
-- ..