为什么'为'从Data.Traversable接受monadic动作?

时间:2016-12-14 19:06:53

标签: haskell 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约束会阻止我在传递给for的操作中使用do notation / return。

我明显遗漏了一些基本的东西,无论是for签名真正意味着什么,或者我发布的代码是如何由编译器解释的,并且会感谢任何帮助理解什么&#39;继续。

2 个答案:

答案 0 :(得分:6)

这只是通常的调用者与实施者的二元性,一方获得灵活性和另一方限制。

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接口中的多态性的限制。他们必须使用 {/ 1}}的任何选项,因此他们只能 在他们编写的代码中使用f接口来实现{{1} }}。但这仅限制Applicative本身的代码,而不是传入其中的函数。

如果for的作者想要限制调用者在该函数中可以执行的操作,他们可以使用for代替提供此接口:

for

现在提供的lambda本身必须是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的实现者可以自由地使用它们喜欢的任何类型(包括可能使用monad功能将其与其他内部值组合)。使用此特定类型签名时,Applicative的实施者必须选择使用与for选择的for调用方相同类型的g实例化,以便提出最终的for返回值。但f的调用者仍然会被类型系统限制为提供适用于任何f (t b)的函数。

关键是,如果您要选择用于实例化多态签名的类型,那么您就不是那个受该接口限制的类型。您可以选择一种类型,然后使用您喜欢的任何其他类型的功能,前提是您仍然提供了界面所需的信息。即,您可以使用非for功能创建Applicative g和非Traversable功能来创建t a,所需要的只是您确实提供了这些输入。事实上,您几乎拥有来使用特定于Applicativea -> f b的功能。多态签名的实现者没有获得这种自由,他们受到多态性的限制,只能处理任何可能的选择。

顺便说一句,类似于排名2类型如何将这种二元性的“另一个级别”与角色颠倒相加(并且排名N类型允许任意多个级别),在约束中也看到类似的二元性(再次翻转)他们自己。签名再次考虑:

a

选择bfor :: (Traversable t, Applicative f) => t a -> (a -> f b) -> f (t b) 类型时,for来电TraversableApplicative限制条件的限制。实现者可以自由使用这些约束所隐含的任何函数,而无需担心如何证明约束条件得到满足。

答案 1 :(得分:5)

第一个简短回答是ParserApplicative个实例。摘录

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'对的类型)

第二个简短的回答是,MonadApplicative更强大,在您需要Applicative的任何地方,您都可以使用Monad。自Monad-Applicative proposal实施以来,每个 Monad也是ApplicativeMonad类现在看起来像

class Applicative m => Monad m where 
    ...

MonadApplicative更强大,只要您需要Applicative,就可以使用Monad代替以下替换:

  • ap代替<*>
  • return代替pure
  • liftM代替fmap

如果您正在编写一些新类型SomeMonad,并为Monad类提供了一个实例,则可以使用它来为Applicative和{{1}提供实例也是。

Functor