用于获取或设置由运行时参数确定的记录字段的镜头

Mar*_*ann 2 haskell lenses haskell-lens

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

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)
Run Code Online (Sandbox Code Playgroud)

我正在做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 }
Run Code Online (Sandbox Code Playgroud)

如所示,我的问题不是我不能实现这些功能。

但是,它们对我来说确实像镜头一样,所以我想知道是否可以通过lens库获得该功能?

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

dan*_*iaz 6

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

如果我们PointsData对的类型进行多态处理Points,则它将适合Representabletypeclass。我们可以说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)
Run Code Online (Sandbox Code Playgroud)


Fyo*_*kin 5

您可以创建一个产生Lens给定a 的函数Player,如下所示:

playerPoints :: Player -> Lens' PointsData Point
playerPoints PlayerOne = field @"pointsToPlayerOne"
playerPoints PlayerTwo = field @"pointsToPlayerTwo"
Run Code Online (Sandbox Code Playgroud)

(这是field从中generic-lens使用

用法如下:

pts :: PointsData

pl1 = pts ^. playerPoints PlayerOne
pl2 = pts ^. playerPoints PlayerTwo

newPts = pts & playerPoints PlayerOne .~ 42
Run Code Online (Sandbox Code Playgroud)

PS或者您是否正在寻找PointsData通过将字段名称与Player构造函数名称匹配来选择字段?也可以通过来实现Generic,但似乎并不值得。