2014-05-02 26 views
7

Mam problem z używaniem biblioteki lens dla konkretnego problemu. Próbuję przekazaćUżywanie soczewki dwa razy

  1. uaktualniony struktura danych
  2. obiektyw koncentruje się na części tej zaktualizowanej struktury

do innej funkcji, g. Przesyłam zarówno obiektyw, jak i strukturę danych, ponieważ g potrzebuje pewnych wspólnych informacji ze struktury danych, a także z informacji. (Jeśli to pomaga, struktura danych zawiera informacje o wspólnym rozkładzie prawdopodobieństwa, ale g działa tylko na marginesie i musi wiedzieć, na którym marginalnie patrzę.Jednaką różnicą między tymi dwoma marginesami jest ich średnia z resztą ich definicja jest współużytkowana w strukturze danych).

Moja pierwsza próba wyglądało to

f :: Functor f => Params -> ((Double -> f Double) -> Params -> f Params) -> a 
f p l = g (l %~ upd $ p) l 
    where upd = ... 

g p x = go p p^.x 

ale to zawiedzie podczas kompilacji, ponieważ f dostaje wywnioskować jako Identity dla aktualizacji i Const Double dla getter.

Jaki jest najlepszy sposób na osiągnięcie tego, co chcę zrobić? Mogę sobie wyobrazić, jest w stanie wykonać jedną z następujących czynności:

  1. zrobić kopię obiektywu tak, że typ wnioskowanie może być różny w każdym przypadku
  2. zamiast przechodząc zaktualizowaną strukturę i obiektyw, mijam oryginalna struktura i soczewka, która zwraca zmodyfikowaną wartość (jeśli chcę tylko zaktualizować część struktury, na którą patrzy obiektyw).
  3. czyni lepszym wyborem dla mojego projektu konstrukcji funkcje/dane
  4. coś zupełnie innego

Dzięki za pomoc!

Odpowiedz

9

András Kovács answer pokazuje, jak to osiągnąć, używając RankNTypes.Jeśli chcesz uniknąć RankNTypes, a następnie można użyć ALens i cloneLens:

f :: a -> ALens' a Int -> (Int, a) 
f a l = (newvalue, a & cloneLens l .~ newvalue) 
    where oldvalue = a^.cloneLens l 
     newvalue = if oldvalue == 0 then 0 else oldvalue - 1 

Control.Lens.Loupe zapewnia operatorów i funkcji, które działają na ALens zamiast Lens.

Należy zauważyć, że w wielu przypadkach, należy również mieć możliwość korzystania z <<%~, który jest jak %~ ale także zwraca starą wartość lub <%~, która zwraca nową wartość:

f :: a -> LensLike' ((,) Int) a Int -> (Int, a) 
f a l = a & l <%~ g 
    where g oldvalue = if oldvalue == 0 then 0 else oldvalue - 1 

Ma to tę zaletę, może również pracować z Isos lub czasami także z Traversals (gdy typem docelowym jest Monoid).

7

Chcesz, aby Twój podpis typ wyglądać tak:

f :: Params -> Lens Params Params Double Double -> ... 
-- alternatively, instead of the long Lens form you can write 
-- Lens' Params Double 

To nie jest równoznaczne z tym, co napisałem w podpisie, ponieważ parametr funktor jest ilościowo wewnątrz Lens:

type Lens s t a b = forall f. Functor f => (a -> f b) -> (s -> f t) 

Poprawny podpis przekłada się na:

f :: Params -> (forall f. Functor f => (Double -> f Double) -> Params -> f Params) -> ... 

Thi s uniemożliwia kompilatorowi ujednolicenie różnych parametrów różnych zastosowań obiektywu, tj. mi. możesz używać soczewki polimorficznie. Zauważ, że potrzebujesz rozszerzenia RankNTypes lub Rank2Types GHC, aby móc napisać podpis.

2

Benno podał najlepszą ogólną odpowiedź.

Są jeszcze dwie inne opcje, które tutaj przedstawię dla kompletności.

1.)

Istnieje kilka Loupe kombinatorów w Lens.

http://hackage.haskell.org/package/lens-4.1.2/docs/Control-Lens-Loupe.html

Wszyscy mają nazwy, które dotyczą #.

^# i #%= oba biorą ALens, który jest obiektywem utworzonym przy konkretnym konkretnym wyborze funktora.

Może to być użyteczne, jeśli chcesz przekazać listę soczewek lub naprawdę potrzebujesz kilku przejść.

2.)

Inną opcją, a moja ulubioną taktyką jest, aby dowiedzieć się, jak wykonać obie operacje na ten sam czas.

Tutaj modyfikujesz, ale chcesz ustawić wartość, którą właśnie ustawiłeś. Tak, możesz dać to, używając <%~ zamiast %~.

Teraz tylko tworzysz instancję obiektywu przy jednym wyborze funktora, a twój kod jest szybszy.