Свободная альтернатива в структурном исполнении без левого дистрибутива
В отличном бесплатном пакете есть хорошая бесплатная альтернатива, которая поднимает Functor к левой дистрибутивной альтернативе.
То есть претензия такова:
runAlt :: Alternative g => (forall x. f x -> g x) -> Alt f a -> g a
Альтернативный гомоморфизм, с liftAlt
, И, действительно, это один, но только для экземпляров Альтернативы с левым распределением.
Конечно, на самом деле, очень немногие экземпляры Alternative являются фактически левораспределительными. Большинство альтернативных экземпляров, которые действительно имеют значение (парсеры, MaybeT f для большинства монад f и т. Д.), Не являются дистрибутивными слева. Этот факт можно показать на примере, где runAlt
а также liftAlt
Не формируйте альтернативный гомоморфизм:
(writeIORef x False <|> writeIORef True) *> (guard =<< readIORef x)
-- is an IO action that throws an exception
runAlt id $ (liftAlt (writeIORef x False) <|> liftAlt (writeIORef True))
*> liftAlt (guard =<< readIORef x)
-- is an IO action that throws no exception and returns successfully ()
Так runAlt
является только Гомоморфизмом Альтернатив для некоторых Альтернатив, но не для всех. Это потому, что структура Alt
нормализует все действия по левому распределению.
Alt
это здорово, потому что, структурно, Alt f
является законным Applicative
а также Alternative
, Нет никакого возможного способа построить значение типа Alt f a
используя функции Applicative и Alternative, которые не подчиняются законам... структура самого типа - это то, что делает его бесплатной альтернативой.
Так же, как для списков, вы не можете создать список с помощью <>
а также mempty
это не уважает x <> mempty = x
, mempty <> x = x
и ассоциативность.
Я написал бесплатную альтернативу, которая не обеспечивает структурное применение Применимого и Альтернативного законов, но дает действительный гомоморфизм Альтернатив и Приложений с помощью runAlt/liftAlt:
data Alt :: (* -> *) -> * -> * where
Pure :: a -> Alt f a
Lift :: f a -> Alt f a
Empty :: Alt f a
Ap :: Alt f (a -> b) -> Alt f a -> Alt f b
Plus :: Alt f as -> Alt f as -> Alt f as
instance Functor f => Functor (Alt f) where
fmap f = \case
Pure x -> Pure (f x)
Lift x -> Lift (f <$> x)
Empty -> Empty
Ap fs xs -> Ap ((f .) <$> fs) xs
Plus xs ys -> Plus (f <$> xs) (f <$> ys)
instance Functor f => Applicative (Alt f) where
pure = Pure
(<*>) = Ap
instance Functor f => Alternative (Alt f) where
empty = Empty
(<|>) = Plus
структурно, Alt f
не факт Applicative
, так как:
pure f <*> pure x = Ap (Pure f) (Pure x)
pure (f x) = Pure (f x)
Так pure f <*> pure x
это не то же самое, что pure (f x)
конструктивно. Не действительный заявитель, сразу же.
Но с учетом runAlt
а также liftAlt
:
liftAlt :: f a -> Alt f a
liftAlt = Lift
runAlt :: Alternative g => (forall x. f x -> g x) -> Alt f a -> g a
runAlt f = \case
Pure x -> pure x
Lift x -> f x
Empty -> empty
Ap fs xs -> runAlt f fs <*> runAlt f xs
Plus xs ys -> runAlt f xs <|> runAlt f ys
А также runAlt
здесь действительно действует как действительный аппликативный гомоморфизм с данным естественным преобразованием...
Можно сказать, что мой новый Alt f
является действительным Альтернативным и Применимым, когда определяется отношением эквивалентности, определяемым runAlt
, Я полагаю.
Во всяком случае, это только немного неудовлетворительно. Есть ли способ написать бесплатную Альтернативу, которая является структурно допустимой Альтернативой и Аппликацией, без применения левой дистрибутивности?
(В частности, я на самом деле заинтересован в том, чтобы следовать закону левого улова и применять его структурно. Это было бы отдельной и интересной вещью, но не полностью необходимой.)
И, если нет никакого пути, почему бы и нет?
1 ответ
Control.Alternative.Free
"s Alt f
производит левый дистрибутив Alternative
бесплатно, даже если f
не Alternative
или же f
это не левый дистрибутив Alternative
, Можно сказать, что в дополнение к хорошо согласованным альтернативным законам
empty <|> x = x
x <|> empty = x
(x <|> y) <|> z = x <|> (y <|> z)
empty <*> f = empty
Alt f
также дает левую раздачу бесплатно.
(a <|> b) <*> c = (a <*> c) <|> (b <*> c)
Так как Alt f
всегда остается дистрибутивным (и runAlt . liftAlt = id
) liftAlt
никогда не может быть гомоморфизмом для не левой дистрибутивной Alternative
s. Если Alternative f
не левый дистрибутив, то существует a
, b
, а также c
такой, что
(a <|> b) <*> c != (a <*> c) <|> (b <*> c)
Если liftAlt : f -> Alt f
был гомоморфизм тогда
(a <|> b) <*> c != (a <*> c) <|> (b <*> c) -- f is not left-distributive
id ((a <|> b) <*> c) != id ((a <*> c) <|> (b <*> c))
runAlt . liftAlt ((a <|> b) <*> c) != runAlt . liftAlt ((a <*> c) <|> (b <*> c)) -- runAlt . liftAlt = id
runAlt ((liftAlt a <|> liftAlt b) <*> liftAlt c) != runAlt ((liftAlt a <*> liftAlt c) <|> (liftAlt b <*> liftAlt c)) -- homomorphism
runAlt ((liftAlt a <|> liftAlt b) <*> liftAlt c) != runAlt ((liftAlt a <|> liftAlt b) <*> liftAlt c) -- by left-distribution of `Alt`, this is a contradiction
Чтобы продемонстрировать это нам нужен Alternative
это не левый дистрибутив. Вот один, FlipAp []
,
newtype FlipAp f a = FlipAp {unFlipAp :: f a}
deriving Show
instance Functor f => Functor (FlipAp f) where
fmap f (FlipAp x) = FlipAp (fmap f x)
instance Applicative f => Applicative (FlipAp f) where
pure = FlipAp . pure
(FlipAp f) <*> (FlipAp xs) = FlipAp ((flip ($) <$> xs) <*> f)
instance Alternative f => Alternative (FlipAp f) where
empty = FlipAp empty
(FlipAp a) <|> (FlipAp b) = FlipAp (a <|> b)
Наряду с некоторыми законами для левого и правого распределения, а также с некоторыми примерами
leftDist :: Alternative f => f (x -> y) -> f (x -> y) -> f x -> Example (f y)
leftDist a b c = [(a <|> b) <*> c, (a <*> c) <|> (b <*> c)]
rightDist :: Alternative f => f (x -> y) -> f x -> f x -> Example (f y)
rightDist a b c = [a <*> (b <|> c), (a <*> b) <|> (a <*> c)]
type Example a = [a]
ldExample1 :: Alternative f => Example (f Int)
ldExample1 = leftDist (pure (+1)) (pure (*10)) (pure 2 <|> pure 3)
rdExample1 :: Alternative f => Example (f Int)
rdExample1 = rightDist (pure (+1) <|> pure (*10)) (pure 2) (pure 3)
Мы можем продемонстрировать несколько свойств списков, FlipAp
списки и runAlt
,
Списки распределены слева, но FlipAp
списки не
ldExample1 :: Example [Int]
ldExample1 :: Example (FlipAp [] Int)
[[3,4,20,30],[3,4,20,30]]
[FlipAp {unFlipAp = [3,20,4,30]},FlipAp {unFlipAp = [3,4,20,30]}]
Списки не правораспределительные, но FlipAp
списки
rdExample1 :: Example [Int]
rdExample1 :: Example (FlipAp [] Int)
[[3,4,20,30],[3,20,4,30]]
[FlipAp {unFlipAp = [3,20,4,30]},FlipAp {unFlipAp = [3,20,4,30]}]
Alt
всегда левый дистрибутив
map (runAlt id) ldExample1 :: Example [Int]
map (runAlt id) ldExample1 :: Example (FlipAp [] Int)
[[3,4,20,30],[3,4,20,30]]
[FlipAp {unFlipAp = [3,4,20,30]},FlipAp {unFlipAp = [3,4,20,30]}]
Alt
никогда не распределяется правильно
map (runAlt id) rdExample1 :: Example [Int]
map (runAlt id) rdExample1 :: Example (FlipAp [] Int)
[[3,4,20,30],[3,20,4,30]]
[FlipAp {unFlipAp = [3,4,20,30]},FlipAp {unFlipAp = [3,20,4,30]}]
Мы можем осквернить правильную дистрибутивную бесплатную альтернативу с точки зрения FlipAp
а также Alt
,
runFlipAlt :: forall f g a. Alternative g => (forall x. f x -> g x) -> FlipAp (Alt f) a -> g a
runFlipAlt nt = runAlt nt . unFlipAp
FlipAp
Alt
никогда не остается дистрибутивным.
map (runFlipAlt id) ldExample1 :: Example [Int]
map (runFlipAlt id) ldExample1 :: Example (FlipAp [] Int)
[[3,20,4,30],[3,4,20,30]]
[FlipAp {unFlipAp = [3,20,4,30]},FlipAp {unFlipAp = [3,4,20,30]}]
FlipAp
Alt
всегда правораспределительный
map (runFlipAlt id) rdExample1 :: Example [Int]
map (runFlipAlt id) rdExample1 :: Example (FlipAp [] Int)
[[3,20,4,30],[3,20,4,30]]
[FlipAp {unFlipAp = [3,20,4,30]},FlipAp {unFlipAp = [3,20,4,30]}]
До сих пор я не говорил вам ничего, что вы не подразумевали, говоря, что liftAlt : f -> Alt f
является Alternative
гомоморфизм, но только для левых дистрибутивных альтернативных случаев. Но я показал вам бесплатную альтернативу, которая не является дистрибутивной слева (вместо этого она тривиально правой дистрибутивной).
Структурно действующий бесплатный Alternative
Этот раздел отвечает на большую часть вашего вопроса, есть ли структурно действующий бесплатный Alternative
это не левый дистрибутив? Да.
Это не эффективная реализация; его цель - продемонстрировать, что он существует и что к какой-то его версии можно прийти прямым путем.
Сделать структурно действительным бесплатный Alternative
Я делаю две вещи. Во-первых, создать структуру данных, которая не может представлять Alternative
законы; если он не может представлять закон, то структура не может быть построена независимо от класса типа, чтобы нарушить его. Это тот же трюк, который используется для составления списков, подчиняющихся Alternative
закон ассоциативности; нет списка, который может представлять лево-связанный (x <|> y) <|> z
, Вторая часть - убедиться, что операции подчиняются законам. Список не может представлять левый закон об ассоциации, но является реализацией <|>
может все еще нарушать это, как x <|> y = x ++ reverse y
,
Следующая структура не может быть построена для представления любого из Alternative
законы.
{-# Language GADTs #-}
{-# Language DataKinds #-}
{-# Language KindSignatures #-}
data Alt :: (* -> *) -> * -> * where
Alt :: Alt' empty pure plus f a -> Alt f a
-- empty pure plus
data Alt' :: Bool -> Bool -> Bool -> (* -> *) -> * -> * where
Empty :: Alt' True False False f a
Pure :: a -> Alt' False True False f a
Lift :: f a -> Alt' False False False f a
Plus :: Alt' False pure1 False f a -> Alt' False pure2 plus2 f a -> Alt' False False True f a
-- Empty can't be to the left or right of Plus
-- empty <|> x = x
-- x <|> empty = x
-- Plus can't be to the left of Plus
-- (x <|> y) <|> z = x <|> (y <|> z)
Ap :: Alt' False False plus1 f (a -> b) -> Alt' empty False plus2 f a -> Alt' False False False f b
-- Empty can't be to the left of `Ap`
-- empty <*> f = empty
-- Pure can't be to the left or right of `Ap`
-- pure id <*> v = v
-- pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
-- pure f <*> pure x = pure (f x)
-- u <*> pure y = pure ($ y) <*> u
Это Functor
instance Functor f => Functor (Alt' empty pure plus f) where
fmap _ Empty = Empty
fmap f (Pure a) = Pure (f a)
fmap f (Plus a as) = Plus (fmap f a) (fmap f as)
fmap f (Lift a) = Lift (fmap f a)
fmap f (Ap g a) = Ap (fmap (f .) g) a
instance Functor f => Functor (Alt f) where
fmap f (Alt a) = Alt (fmap f a)
И его Applicative
, Поскольку структура не может представлять законы, когда мы сталкиваемся с термином, содержащим одно из непригодных для выражения выражений, мы вынуждены преобразовать его во что-то другое. Законы говорят нам, что делать.
instance Functor f => Applicative (Alt f) where
pure a = Alt (Pure a)
Alt Empty <*> _ = Alt Empty -- empty <*> f = empty
Alt (Pure f) <*> (Alt x) = Alt (fmap f x) -- pure f <*> x = fmap f x (free theorem)
Alt u <*> (Alt (Pure y)) = Alt (fmap ($ y) u) -- u <*> pure y = pure ($ y) <*> u
Alt f@(Lift _) <*> Alt x@Empty = Alt (Ap f x)
Alt f@(Lift _) <*> Alt x@(Lift _) = Alt (Ap f x)
Alt f@(Lift _) <*> Alt x@(Plus _ _) = Alt (Ap f x)
Alt f@(Lift _) <*> Alt x@(Ap _ _) = Alt (Ap f x)
Alt f@(Plus _ _) <*> Alt x@Empty = Alt (Ap f x)
Alt f@(Plus _ _) <*> Alt x@(Lift _) = Alt (Ap f x)
Alt f@(Plus _ _) <*> Alt x@(Plus _ _) = Alt (Ap f x)
Alt f@(Plus _ _) <*> Alt x@(Ap _ _) = Alt (Ap f x)
Alt f@(Ap _ _) <*> Alt x@Empty = Alt (Ap f x)
Alt f@(Ap _ _) <*> Alt x@(Lift _) = Alt (Ap f x)
Alt f@(Ap _ _) <*> Alt x@(Plus _ _) = Alt (Ap f x)
Alt f@(Ap _ _) <*> Alt x@(Ap _ _) = Alt (Ap f x)
Все из тех Ap
Они могут быть покрыты парой шаблонов вида, но это не делает их проще.
Это также Alternative
, Для этого мы будем использовать шаблон представления, чтобы разделить случаи на пустые и непустые случаи, и дополнительный тип для хранения доказательства того, что они не пустые.
{-# Language ViewPatterns #-}
import Control.Applicative
data AltEmpty :: (* -> *) -> * -> * where
Empty_ :: Alt' True False False f a -> AltEmpty f a
NonEmpty_ :: AltNE f a -> AltEmpty f a
data AltNE :: (* -> *) -> * -> * where
AltNE :: Alt' False pure plus f a -> AltNE f a
empty_ :: Alt' e1 p1 p2 f a -> AltEmpty f a
empty_ x@Empty = Empty_ x
empty_ x@(Pure _) = NonEmpty_ (AltNE x)
empty_ x@(Lift _) = NonEmpty_ (AltNE x)
empty_ x@(Plus _ _) = NonEmpty_ (AltNE x)
empty_ x@(Ap _ _) = NonEmpty_ (AltNE x)
instance Functor f => Alternative (Alt f) where
empty = Alt Empty
Alt Empty <|> x = x -- empty <|> x = x
x <|> Alt Empty = x -- x <|> empty = x
Alt (empty_ -> NonEmpty_ a) <|> Alt (empty_ -> NonEmpty_ b) = case a <> b of AltNE c -> Alt c
where
(<>) :: AltNE f a -> AltNE f a -> AltNE f a
AltNE (Plus x y) <> AltNE z = AltNE x <> (AltNE y <> AltNE z) -- (x <|> y) <|> x = x <|> (y <|> z)
AltNE a@(Pure _) <> AltNE b = AltNE (Plus a b)
AltNE a@(Lift _) <> AltNE b = AltNE (Plus a b)
AltNE a@(Ap _ _) <> AltNE b = AltNE (Plus a b)
liftAlt
а также runAlt
{-# Language RankNTypes #-}
{-# Language ScopedTypeVariables #-}
liftAlt :: f a -> Alt f a
liftAlt = Alt . Lift
runAlt' :: forall f g x empty pure plus a. Alternative g => (forall x. f x -> g x) -> Alt' empty pure plus f a -> g a
runAlt' u = go
where
go :: forall empty pure plus a. Alt' empty pure plus f a -> g a
go Empty = empty
go (Pure a) = pure a
go (Lift a) = u a
go (Plus x y) = go x <|> go y
go (Ap f x) = go f <*> go x
runAlt :: Alternative g => (forall x. f x -> g x) -> Alt f a -> g a
runAlt u (Alt x) = runAlt' u x
Это новый Alt f
не предоставляет ни левого, ни правого распределения бесплатно, и поэтому runAlt id :: Alt f a -> g a
сохраняет как дистрибутив g
является.
Списки все еще левые, но FlipAp
списки не являются.
map (runAlt id) ldExample1 :: Example [Int]
map (runAlt id) ldExample1 :: Example (FlipAp [] Int)
[[3,4,20,30],[3,4,20,30]]
[FlipAp {unFlipAp = [3,20,4,30]},FlipAp {unFlipAp = [3,4,20,30]}]
Списки не правораспределительные, но FlipAp
списки еще есть
map (runAlt id) rdExample1 :: Example [Int]
map (runAlt id) rdExample1 :: Example (FlipAp [] Int)
[[3,4,20,30],[3,20,4,30]]
[FlipAp {unFlipAp = [3,20,4,30]},FlipAp {unFlipAp = [3,20,4,30]}]
Исходный код для этого раздела
Конструктивно действительный левый улов бесплатно Alternative
Чтобы контролировать, какие законы мы хотим, мы можем добавить их к структурно бесплатной альтернативе, которую мы сделали ранее.
Чтобы добавить левый улов, мы изменим структуру, чтобы она не могла ее представить. Левый улов
(чистый а) <|> х = чистый а
Хранить Alt'
из представления мы исключим pure
от того, что разрешено слева от Plus
,
-- empty pure plus
data Alt' :: Bool -> Bool -> Bool -> (* -> *) -> * -> * where
Empty :: Alt' True False False f a
Pure :: a -> Alt' False True False f a
Lift :: f a -> Alt' False False False f a
Plus :: Alt' False False False f a -> Alt' False pure2 plus2 f a -> Alt' False False True f a
-- Empty can't be to the left or right of Plus
-- empty <|> x = x
-- x <|> empty = x
-- Plus can't be to the left of Plus
-- (x <|> y) <|> z = x <|> (y <|> z)
-- Pure can't be to the left of Plus
-- (pure a) <|> x = pure a
...
Это приводит к ошибке компилятора в реализации Alternative Alt
Couldn't match type ‘'True’ with ‘'False’
Expected type: Alt' 'False 'False 'False f a1
Actual type: Alt' 'False pure2 plus2 f a1
In the first argument of ‘Plus’, namely ‘a’
In the first argument of ‘AltNE’, namely ‘(Plus a b)
Что мы можем исправить, обратившись к нашему новому закону, (pure a) <|> x = pure a
instance Functor f => Alternative (Alt f) where
empty = Alt Empty
Alt Empty <|> x = x -- empty <|> x = x
x <|> Alt Empty = x -- x <|> empty = x
Alt (empty_ -> NonEmpty_ a) <|> Alt (empty_ -> NonEmpty_ b) = case a <> b of AltNE c -> Alt c
where
(<>) :: AltNE f a -> AltNE f a -> AltNE f a
AltNE a@(Pure _) <> _ = AltNE a -- (pure a) <|> x = pure a
AltNE (Plus x y) <> AltNE z = AltNE x <> (AltNE y <> AltNE z) -- (x <|> y) <|> x = x <|> (y <|> z)
AltNE a@(Lift _) <> AltNE b = AltNE (Plus a b)
AltNE a@(Ap _ _) <> AltNE b = AltNE (Plus a b)