Почему "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, Реализатор получает свободу использования любых функций, подразумеваемых этими ограничениями, не беспокоясь о том, как доказать, что ограничения выполнены.

Другие вопросы по тегам