Как сделать изделие из двух линз?

Если у меня два объектива:

foo :: Lens' X Foo
bar :: Lens' X Bar

Есть ли способ построить линзу продукта:

foobar :: Lens' X (Foo, Bar)
foobar = ... foo bar 

или это невозможно?


person phadej    schedule 09.04.2016    source источник


Ответы (2)


В общем случае это невозможно. Наверное, самый распространенный случай, когда у вас линзы на разные поля записи, линзы не пересекаются, поэтому можно сделать законопослушную линзу. Но в целом это неправда. Вот почему комбинатор не предусмотрен в библиотеках, даже если бы его было легко написать.

Предположим, что lensProd существует. Достаточно взять один и тот же объектив дважды:

_1 :: Lens' (a, b) a -- Simpler type

badLens :: Lens' (a, b) (a, a)
badLens = lensProd _1 _1

Тогда закон «Вы получите обратно то, что вложили» не работает. Должен быть:

view badLens (set badLens (1, 2) (3, 4)) ≡ (1, 2)

Но это не может быть правдой, так как view badLens pair возвращает некоторое значение дважды: (x, x) для всех pair.

@dfeuer дает пример, как определить lensProd.


Интересно, что дуал тоже сломан. В общем случае у вас не может быть законной суммы призмы:

{-# LANGUAGE RankNTypes #-}

import Control.Applicative
import Control.Lens

-- |
-- >>> :t sumPrism _Just _Nothing :: Prism' (Maybe a) (Either a ())
-- sumPrism _Just _Nothing :: Prism' (Maybe a) (Either a ())
--  :: (Applicative f, Choice p) =>
--     p (Either a ()) (f (Either a ())) -> p (Maybe a) (f (Maybe a))
--
sumPrism :: Prism' a b -> Prism' a c -> Prism' a (Either b c)
sumPrism ab ac = prism' build match where
    build (Left b)  = ab # b
    build (Right c) = ac # c

    match x = Left <$> x ^? ab <|> Right <$>  x ^? ac

-- The law
--
-- @
-- preview l (review l b) ≡ Just b
-- @
--
-- breaks with
--
-- >>> preview badPrism (review badPrism (Right 'x'))
-- Just (Left 'x')
-- 
badPrism :: Prism' a (Either a a)
badPrism = sumPrism id id

Как видите, мы вставили Right, а получили Left.

person phadej    schedule 09.04.2016
comment
Это точно двойное? - person dfeuer; 19.10.2017
comment
Как насчет суммы линз и произведения призм? - person ferranpujolcamins; 19.04.2019
comment
сумма линз, например hackage.haskell .org/package/lens-4.17/docs/ ? - person phadej; 19.04.2019

Как объяснил Фадей, законопослушного способа сделать это вообще не существует. Тем не менее, вы все равно можете это сделать и предупредить своих пользователей, что им следует быть осторожными, применяя его только к ортогональным линзам.

import Control.Lens
import Control.Arrow ((&&&))

fakeIt :: Lens' s x -> Lens' s y -> Lens' s (x,y)
fakeIt l1 l2 =
  lens (view l1 &&& view l2)
       (\s (x,y) -> set l1 x . set l2 y $ s)

Например:

Prelude Control.Lens A> set (fakeIt _1 _2) (7,8) (1,2,3)
(7,8,3)
Prelude Control.Lens A> view (fakeIt _1 _2) (1,2,3)
(1,2)
person dfeuer    schedule 10.04.2016
comment
Мы могли бы получить доказательство непересекаемости на уровне типа, как я думаю, в compdata, и комбинатор, зависящий от предоставления такого доказательства. - person nicolas; 14.12.2016