我有这些类型(还有更多):

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库获得该功能?

大多数镜头教程都展示了如何获取或设置更大数据结构中特定的命名部分。这似乎不太适合我在这里要做的事情;相反,我试图获取或设置在运行时确定的子部件。

最佳答案

游览有点抽象的类型类。您的PointsDataPlayer类型有特殊关系。它有点像Map Player Point,其特殊之处在于,对于每个Player可能的值,总会有一个对应的Point。在某种程度上,PointsData就像一个“ reified函数” Player -> Point

如果我们使PointsDataPoints的类型上是多态的,则它将与Representable类型类匹配。我们可以说PointsDataPlayer表示。

Representable通常用作表格式数据的接口,例如grids包中的接口。



因此,一种可能的解决方案是将PointsData转换为实际的Map,但将实现隐藏在智能构造函数的后面,该构造函数使用Player -> Point函数针对所有可能的键对其进行初始化(它将对应于tabulate方法(Representable)。

用户应该不能从地图中删除键。但是我们可以搭载IxedMap实例来提供遍历。

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/

10-14 04:19