J'ai ces types (et plus):
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 }
Je fais le Tennis kata , et dans le cadre de l'implémentation, j'aimerais utiliser certaines fonctions qui me permettent d'obtenir ou de définir les points pour un joueur arbitraire, connu uniquement à l'exécution.
Formellement, j'ai besoin de fonctions comme celles-ci:
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)
Comme démontré, mon problème n'est pas que je ne peux pas implémenter ces fonctions.
Ils le font, cependant, me ressemble lentille , alors je me demande si je pourrais obtenir cette fonctionnalité via la bibliothèque lens
?
La plupart des didacticiels sur les objectifs montrent comment obtenir ou définir une partie nommée particulière d'une structure de données plus grande. Cela ne semble pas tout à fait correspondre à ce que j'essaie de faire ici; j'essaye plutôt d'obtenir ou de définir une sous-partie déterminée au moment de l'exécution.
3 Réponses :
Vous pouvez créer une fonction qui produit un Lens
étant donné un Player
, comme ceci:
pts :: PointsData pl1 = pts ^. playerPoints PlayerOne pl2 = pts ^. playerPoints PlayerTwo newPts = pts & playerPoints PlayerOne .~ 42
(ceci utilise champ
de generic-lens
)
L'utilisation serait comme ceci:
playerPoints :: Player -> Lens' PointsData Point playerPoints PlayerOne = field @"pointsToPlayerOne" playerPoints PlayerTwo = field @"pointsToPlayerTwo"
PS Ou étiez-vous à la recherche d'un champ de PointsData
en faisant correspondre le nom du champ au nom du constructeur de Player
? C'est également possible via Generic
, mais cela ne semble pas valoir la peine.
Note de bas de page: cela fonctionne aussi bien avec les lentilles de lentille - la seule différence est la façon dont les deux lentilles sont définies.
Une excursion dans des classes de types quelque peu abstraites. Votre PointsData
a une relation particulière avec le type Player
. C'est un peu comme un Map Player Point
, avec la particularité que pour chaque valeur possible de Player
, il y a toujours un Point
correspondant. D'une certaine manière, PointsData
est comme une "fonction réifiée" Player -> Point
.
Si nous rendons PointsData
polymorphes sur le type de Points
, il cadrerait avec le Classe de types Représentable
. Nous dirions que PointsData
est "représenté" par Player
.
Représentable
est souvent utile comme interface aux données tabulaires , comme dans le package grids .
Une solution possible serait donc de transformer PointsData
en une Map
réelle, mais de cacher l'implémentation derrière un constructeur intelligent qui a pris un Player -> Point
pour l'initialiser pour toutes les clés possibles (cela correspondrait à la tabulate
méthode de Representable
).
L'utilisateur ne devrait pas pouvoir supprimer clés de la carte. Mais nous pourrions utiliser le Ixed
instance de Map
pour fournir des traversées.
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)
Je trouve que le concept de penser à PointsData
comme une carte correspond étroitement à mes pensées originales, et j'avais espéré qu'il y aurait quelque chose de plus étroitement lié à un tel concept. Puisque, cependant, j'essaie d'explorer des moyens de modéliser des domaines problématiques, je ne considère pas qu'il soit approprié de rendre PointsData
polymorphe.
D'après la réponse de Fyodor Soikin et le commentaire de duplode em>, j'ai fini par utiliser Il peut être utilisé comme ce fragment d'une plus grande fonction: makeLenses
de lens et écrire une fonction qui renvoie l'objectif approprié: score :: Score -> Player -> Score
-- ..
score (Points pd) winner = Points $ pd & playerPoint winner %~ succ
-- ..
data PointsData =
PointsData { _pointsToPlayerOne :: Point, _pointsToPlayerTwo :: Point }
deriving (Eq, Show, Read)
makeLenses ''PointsData
playerPoint :: Player -> Lens' PointsData Point
playerPoint PlayerOne = pointsToPlayerOne
playerPoint PlayerTwo = pointsToPlayerTwo
Une fonction
pointsToPlayer :: Player -> Lens 'PointsData Points
serait-elle suffisante? (Je ne peux pas le tester pour le moment, mais je suis sûr qu'une implémentation directe de cela fonctionnerait tout de suite.)