2017-09-09 7 views
2

Ceci est une version d'un code sur lequel j'ai travaillé, avec les détails supprimés. J'espère que c'est clair ce que j'essaie de faire, mais sinon je peux clarifier. Je suis tout à fait nouveau à l'utilisation des lentilles, et les types compliqués impliqués souvent les faire paraître plus de problèmes que ce qu'ils valent.En boucle sur les lentilles, et en utilisant chacune avec vue et ensemble

-- some data type 
type AType = ... 

data Thing = Th { _v, _w, _x :: AType, _otherStuff :: ... } 

makeLenses ''Thing 

-- some operation that can be performed on corresponding x or w or v values in two Things. 
f :: AType -> AType -> AType 
f = ... 

-- apply f to the corresponding v, and w, and x values in a and b; then store each result in the respective field of a, leaving other fields untouched. 
transform :: Thing -> Thing -> Thing 
transform a b = 
    let transformVorWorX :: Lens' Thing AType -> AType 
     transformVorWorX lens = 
      let vwx = view lens a 
       vwx' = view lens b 
      in f vwx vwx' 

    in foldl (\a' lens -> set lens (transformVorWorX lens) a') a [v,w,x] 

Quand je compile, GHC recrache

Could not deduce (f ~ Identity) 
from the context (Functor f) 
    bound by a type expected by the context: 
      Functor f => (AType -> f AType) -> Thing -> f Thing 
    at ... 
    ‘f’ is a rigid type variable bound by 
     a type expected by the context: 
     Functor f => (AType -> f AType) -> Thing -> f Thing 
     at ... 
Expected type: (AType -> f AType) -> Thing -> f Thing 
    Actual type: ASetter Thing Thing AType AType 
In the first argument of ‘transformVorWorX’, namely ‘lens’ 
In the second argument of ‘set’, namely ‘(transformVorWorX lens)’ 

Pourquoi pas ce travail de code? Je ai trouvé que le remplacement lens avec cloneLens lens permet de compiler, mais je ne sais pas pourquoi, et cela se sent très laid - y at-il une façon plus élégante de faire ce que je veux, c'est peut-être plus général?

Un grand merci

Répondre

5

Lens' est sous le capot quantifiée universellement

type Lens' s a = ∀ f . Functor f => (a -> f a) -> s -> f s 

Ainsi, le type de [v,w,x] devrait être

[∀ f . Functor f => (AType -> f AType) -> Thing -> f Thing] 

- avec un Quantor dans la liste. Ces types sont appelés impredicative types et Haskell ne les prend pas en charge. (Une extension GHC avec ce nom existe depuis un certain temps, mais cela ne fonctionne pas.)

Il existe un moyen relativement simple de contourner le problème: masquer le quanteur dans un newtype. Dans la bibliothèque de lentilles, ceci est ReifiedLens. Avec elle, vous pouvez écrire

transform a b = 
    let transformVorWorX :: ReifiedLens' Thing AType -> AType 
     transformVorWorX (Lens lens) = 
      let vwx = view lens a 
       vwx' = view lens b 
      in f vwx vwx' 
    in foldl (\a' lens -> set (runLens lens) (transformVorWorX lens) a') a 
      [Lens v, Lens w, Lens x] 

Ce code a exactement le comportement d'exécution de celui que vous envisagiez, mais il est clairement assez laid.

La solution alternative que vous avez vous-même trouvé effectivement la même chose, mais est plutôt agréable. Pour voir ce qui se passe réellement là-bas, je vais mettre le cloneLens dans les transformVorWorX:

transform a b = 
    let transformVorWorX :: ALens' Thing AType -> AType 
     transformVorWorX lens = 
      let vwx = view (cloneLens lens) a 
       vwx' = view (cloneLens lens) b 
      in f vwx vwx' 
    in foldl (\a' lens -> set (cloneLens lens) (transformVorWorX lens) a') a [v,w,x] 

La raison pour laquelle cela fonctionne est que, comme ReifiedLens - mais contrairement à Lens-ALens' n'a pas Quantor. C'est juste une instanciation concrète du Functor f, ainsi il ne nécessite pas d'imprédicativité mais vous pouvez directement utiliser v, w et dans une liste [ALens' Thing AType]. L'instance de foncteur est savamment choisie pour et ne perd pas la généralité, donc cloneLens est capable de vous restituer une lentille complète, universellement quantifiée.

Plutôt que de cloner, vous pouvez également utiliser l'opérateur ^#, qui voit directement un ALens et storing au lieu de set:

transform a b = 
    let transformVorWorX :: ALens' Thing AType -> AType 
     transformVorWorX lens = 
      let vwx = a^#lens 
       vwx' = b^#lens 
      in f vwx vwx' 
    in foldl (\a' lens -> storing lens (transformVorWorX lens) a') a [v,w,x] 

Si nous condensons que d'une forme moins bavard, il semble assez bon OMI:

transform a b = foldl updater a [v,w,x] 
where updater a' lens = a' & lens #~ f (a^#lens) (b^#lens) 

Laissez-moi suggérer aussi une solution tout à fait différente: vous définissez une optique unique qui accède toutes les valeurs d'intérêt, à savoir

vwx :: Traversal' Thing AType 
vwx f (Thing в ш к otherVals) 
    = (\в' ш' к' -> Thing в' ш' к' otherVals) 
     <$> f в <*> f ш <*> f к 

au lieu de définir vous-même, vous pouvez également laisser l'aide modèle Haskell faire pour vous:

makeLensesFor [("_v", "vwx"), ("_w", "vwx"), ("_x", "vwx")] ''Thing 

maintenant , une traversée peut être utilisée comme un simple pli, et donc to get a list de toutes les valeurs. Vous pouvez ensuite utiliser un zip standard pour effectuer la transformation des éléments:

transform a b = ... 
where tfmedVals = zipWith f (a^..vwx) (b^..vwx) 

Pour mettre cette liste des valeurs modifiées de retour dans un récipient, la bibliothèque dispose également d'une fonction: partsOf. Et avec cela, toute votre transformation se résume à

transform a b = a & partsOf vwx .~ zipWith f (a^..vwx) (b^..vwx)