Почему "for" из Data.Traversable принимает монадические действия?
Я работал над следующим небольшим фрагментом кода:
import Control.Monad
import Data.Aeson
import qualified Data.HashMap.Strict as HashMap
import Data.Map (Map)
import qualified Data.Map as Map
import GHC.Generics
-- definitions of Whitelisted, WhitelistComment and their FromJSON instances
-- omitted for brevity
data Whitelist = Whitelist
{ whitelist :: Map Whitelisted WhitelistComment
} deriving (Eq, Ord, Show)
instance FromJSON Whitelist where
parseJSON (Object v) =
fmap (Whitelist . Map.fromList) . forM (HashMap.toList v) $ \(a, b) -> do
a' <- parseJSON (String a)
b' <- parseJSON b
return (a', b')
parseJSON _ = mzero
когда я понял, что могу переписать do
блок в аппликативном стиле:
instance FromJSON Whitelist where
parseJSON (Object v) =
fmap (Whitelist . Map.fromList) . forM (HashMap.toList v) $ \(a, b) ->
(,) <$> parseJSON (String a) <*> parseJSON b
parseJSON _ = mzero
и с этим я мог бы также заменить forM
с for
, Перед внесением изменений я переключился на for
первый:
instance FromJSON Whitelist where
parseJSON (Object v) =
fmap (Whitelist . Map.fromList) . for (HashMap.toList v) $ \(a, b) -> do
a' <- parseJSON (String a)
b' <- parseJSON b
return (a', b')
parseJSON _ = mzero
и к моему удивлению это все еще составлено. Учитывая определение for
:
for :: (Traversable t, Applicative f) => t a -> (a -> f b) -> f (t b)
Я думал Applicative
ограничение не позволит мне использовать do notation / return в действии, переданном for
,
Я явно упускаю что-то фундаментальное здесь, с точки зрения того, что for
Подпись действительно подразумевает, или как код, который я опубликовал, интерпретируется компилятором, и был бы признателен за любую помощь в понимании происходящего.
2 ответа
Первый короткий ответ: Parser
имеет Applicative
пример. Фрагмент
do
a' <- parseJSON a
b' <- parseJSON b
return (a', b')
имеет тип Parser (Whitelisted, WhitelistComment)
который объединяет с f b
в подписи типа
for :: (Traversable t, Applicative f) => t a -> (a -> f b) -> f (t b)
Так как есть Applicative Parser
Например, это также удовлетворяет этому ограничению. (Я думаю, что я получил типы для a'
а также b'
право)
Второй короткий ответ: Monad
строго более мощный, чем Applicative
везде, где вам нужно Applicative
Вы можете использовать Monad
вместо. С тех пор Monad
- Applicative
предложение было реализовано, каждый Monad
это также Applicative
, Monad
класс теперь выглядит
class Applicative m => Monad m where
...
Monad
строго более мощный, чем Applicative
везде, где вам нужно Applicative
Вы можете использовать Monad
вместо этого со следующими заменами:
- ап вместо
<*>
return
вместоpure
liftM
вместоfmap
Если вы пишете какой-то новый тип, SomeMonad
и предоставили экземпляр для Monad
класс вы можете использовать его для предоставления экземпляров для Applicative
а также Functor
тоже.
import Control.Monad
instance Applicative SomeMonad where
pure = return
(<*>) = ap
instance Functor SomeMonad where
fmap = liftM
Это просто обычная двойственность caller-vs-Implementer, когда одна сторона получает гибкость, а другая - ограничение.
for
предоставляет вам этот интерфейс:
for :: (Traversable t, Applicative f) => t a -> (a -> f b) -> f (t b)
Вы, как абонент, можете выбирать любой тип f
чтобы создать его, чтобы вы могли использовать его, как если бы это было:
for :: Traversable t => t a -> (a -> Parser b) -> Parser (t b)
Очевидно, что после того, как вы это сделали, нет никаких причин, по которым вы не могли бы Parser
-специфическая функциональность в функции, которую вы передаете for
, в том числе Monad
вещи.
Реализатор for
с другой стороны, ограничивается полиморфизмом в интерфейсе for
, Они должны работать с любым выбором f
поэтому они могут использовать только Applicative
интерфейс в коде, который они пишут для реализации for
, Но это только ограничивает код for
сама по себе, а не функция, переданная в него.
Если автор for
хотел ограничить то, что звонящий мог сделать в этой функции, они могли бы использовать RankNTypes
вместо этого предоставить этот интерфейс:
for :: forall t f. (Traversable t, Applicative f) => t a -> (forall g. Applicative g => a -> g b) -> f (t b)
Теперь сама лямбда должна быть полиморфной в g
(при условии Applicative
ограничение). Вызывающая сторона for
все еще получает возможность выбора f
с ограничителем реализации в использовании только Applicative
функции. Но вызывающая сторона for
является реализацией аргумента функции, так что теперь, когда эта функция сама полиморфна, вызывающая for
ограничено использованием только Applicative
особенности там и исполнитель for
получает свободу использовать его с любым типом, который им нравится (включая, возможно, использование функций монады, чтобы объединить его с другими внутренними ценностями). С этой специфической сигнатурой типа реализатор for
придется выбрать для создания экземпляра g
с тем же типом вызывающего абонента for
выбран для f
, чтобы придумать финал f (t b)
возвращаемое значение Но вызывающая сторона for
будет по-прежнему ограничена системой типов для предоставления функции, которая работает для любого Applicative g
,
Дело в том, что если вы выбираете тип для создания полиморфной подписи, то вы не ограничены этим интерфейсом. Вы можете выбрать тип, а затем использовать любые другие функции этого типа, которые вам нравятся, при условии, что вы по-прежнему предоставляете информацию, необходимую интерфейсу от вас. т.е. вы можете использовать неTraversable
функциональность для создания вашего t a
и неApplicative
функциональность для создания вашего a -> f b
все, что требуется, это предоставить эти входные данные. И действительно, вы почти должны использовать функциональность, специфичную для a
а также b
, Реализатор полиморфной подписи не получает этой свободы, он ограничен полиморфизмом только тем, что делает работу, которая будет работать для любого возможного выбора.
Кроме того, аналогично тому, как типы ранга 2 добавляют "другой уровень" этой двойственности с переставленными ролями (а типы ранга N допускают произвольное количество уровней), аналогичная двойственность также наблюдается (снова переворачивается) в самих ограничениях. Рассмотрим снова подпись:
for :: (Traversable t, Applicative f) => t a -> (a -> f b) -> f (t b)
Вызывающая сторона for
ограничен Traversable
а также Applicative
ограничения при выборе типов t
а также f
, Реализатор получает свободу использования любых функций, подразумеваемых этими ограничениями, не беспокоясь о том, как доказать, что ограничения выполнены.