我有这些类型(还有更多):
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
库获得该功能?大多数镜头教程都展示了如何获取或设置更大数据结构中特定的命名部分。这似乎不太适合我在这里要做的事情;相反,我试图获取或设置在运行时确定的子部件。
最佳答案
游览有点抽象的类型类。您的PointsData
与Player
类型有特殊关系。它有点像Map Player Point
,其特殊之处在于,对于每个Player
可能的值,总会有一个对应的Point
。在某种程度上,PointsData
就像一个“ reified函数” Player -> Point
。
如果我们使PointsData
在Points
的类型上是多态的,则它将与Representable
类型类匹配。我们可以说PointsData
由Player
表示。Representable
通常用作表格式数据的接口,例如grids包中的接口。
因此,一种可能的解决方案是将PointsData
转换为实际的Map
,但将实现隐藏在智能构造函数的后面,该构造函数使用Player -> Point
函数针对所有可能的键对其进行初始化(它将对应于tabulate
方法(Representable
)。
用户应该不能从地图中删除键。但是我们可以搭载Ixed
的Map
实例来提供遍历。
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)
关于haskell - 用于获取或设置由运行时参数确定的记录字段的镜头,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56468091/