Я считаю, что примерно правильная идея для вашего комбинатора в Haskell такова:
merge :: Lens' s t -> Lens' s t -> Lens' s (t, t)
возможно обобщается на
merge :: Lens' s t -> Lens' s t' -> Lens' s (t, t')
так что ваши две цели могут различаться по типу. Мы могли бы «реализовать» это следующим образом, но это обнаружит проблему.
merge l1 l2 = lens pull push where
pull s = (view l1 s, view l2 s)
push s (t1, t2) = set l2 t2 (set l1 t1 s)
В частности, в части Setter
этого уравнения мы явно упорядочиваем способ, которым возвращаем наши значения. Это означает, что мы можем использовать merge
для создания линз, которые нарушают законы линз. В частности, они нарушают закон PUT-GET. Вот контрпример
newtype Only a = Only a
-- A lens focused on the only slot in Only
only :: Lens' (Only a) a
only inj (Only a) = Only <$> inj a
-- We merge this lens with *itself*
testLens :: Lens' (Only a) (a, a)
testLens = merge only only
-- and now it violates the lens laws
> view testLens (Only 1 & testLens .~ (1,2))
(2,2)
Или, проще говоря, если мы merge
соединим линзу с самой собой, то сторона Setter
превратится в два последовательных множества в одном и том же месте. Это означает, что если мы попытаемся установить его с парой различных значений, сохранится только второй набор, и, таким образом, мы нарушим закон PUT-GET.
Библиотека lens
старается избегать недопустимых объективов, поэтому этот комбинатор недоступен. Самое близкое, что у нас есть, это alongside
< /a>, который имеет следующий (ограниченный) тип
alongside :: Lens' s a
-> Lens' s' a'
-> Lens' (s,s') (a,a')
но, как вы можете видеть, это гарантирует, что наш источник также является типом продукта, так что Setter
применяются однозначно к каждой стороне источника. Если бы мы попытались написать dup
Lens
, который мы могли бы скомпоновать с alongside
для сборки merge
, мы столкнемся с той же проблемой, что и раньше.
dup :: Lens' a (a, a) -- there are two possible implementations, neither are lenses
dup1 inj a = fst <$> inj a
dup2 inj a = snd <$> inj a
Теперь все это может быть довольно техническим и бессмысленным с точки зрения Ruby. В Ruby у вас не будет навязываемой компилятором безопасности типов, поэтому вы, скорее всего, смешаете множество правил вместе и добьетесь меньшей строгости. В таком случае может иметь смысл реализовать merge
с его семантикой последовательной записи.
На самом деле, основываясь на приведенных вами примерах, похоже, что контрпример, на который я указал, не может даже произойти в Ruby, поскольку ваш целевой тип является хэшем и уже обеспечивает уникальные ключи.
Но важно отметить, что merge
можно использовать для создания неподходящих линз, если вы посмотрите на это достаточно внимательно. По сути, это означает, что в более сложных сценариях использование merge
может легко привести к странным ошибкам, вызванным нарушением нашего интуитивного понимания того, что такое объектив. Это может не быть большой проблемой для Ruby, поскольку сложная абстракция в любом случае осуждается в сообществе Ruby, но именно поэтому у нас есть законы.
person
J. Abrahamson
schedule
04.04.2014
zipLenses
илиmerge
подойдет. В Haskell это может выглядеть примерно так:merge :: Lens' s a -> Lens' s b -> Getter s (a, b)
;merge l1 l2 = to (\x -> (view l1 x, view l2 x))
. Очевидно, что это не будет работать какSetter
, так что это не может быть полноценнымLens
. - person bheklilr   schedule 04.04.2014