В большом бесплатном пакете есть хорошая "Бесплатная альтернатива" , которая поднимает Functor на альтернативу слева-дистрибутива.
То есть утверждение состоит в том, что:
runAlt :: Alternative g => (forall x. f x -> g x) -> Alt f a -> g a
- альтернативный гомоморфизм с liftAlt
. И действительно, это одно, но только для left-distributive Альтернативные экземпляры.
Конечно, на самом деле очень мало альтернативных экземпляров на самом деле оставлено - дистрибутив. Большинство альтернативных экземпляров, которые на самом деле имеют значение (парсеры, MaybeT f для большинства Monad 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 ()
So runAlt
является лишь альтернативным гомоморфизмом для некоторых Альтернатив, но не для всех. Это связано с тем, что структура Alt
нормализует все действия для распространения по левому краю.
Alt
велико, потому что структурно Alt f
является законным Applicative
и Alternative
. Нет никакого способа построить значение типа Alt f a
с использованием Аппликативной и Альтернативной функций, которые не следуют законам... сама структура самого типа делает ее свободной Альтернативой.
Так же, как и для списков, вы не можете создать список, используя <>
и 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
, предположим.
Во всяком случае, это лишь немного неудовлетворительно. Есть ли способ написать бесплатную альтернативу, которая является структурно действительной альтернативной и аппликативной, без соблюдения левой дистрибутивности?
(В частности, меня действительно интересует тот, который следует за законом left catch и навязывает его структурно. Это будет отдельный а также интересная вещь, но не полностью необходимая.)
И, если нет, почему бы и нет?