为什么 Data.Traversable 中的 'for' 接受单子动作?

Why does 'for' from Data.Traversable accept monadic actions?

我正在处理以下一小段代码:

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

我还可以用 for 替换 forM。在进行上述更改之前,我先切换到 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 的操作中使用符号 / return。

我在这里显然遗漏了一些基本的东西,无论是 for 签名的真正含义,还是编译器如何解释我发布的代码,如果能帮助我理解正在发生的事情,我将不胜感激。

第一个简短的回答是 Parser 有一个 Applicative 实例。片段

do
  a' <- parseJSON a
  b' <- parseJSON b
  return (a', b')

在类型签名

中具有与f b统一的类型Parser (Whitelisted, WhitelistComment)
for :: (Traversable t, Applicative f) => t a -> (a -> f b) -> f (t b)

因为有一个 Applicative Parser 实例,它也满足该约束。 (我想我得到了 a'b' 的正确类型)


第二个简短的回答是 Monad 严格来说比 Applicative 更强大,任何需要 Applicative 的地方都可以使用 Monad。于是乎 Monad-Applicative proposal was implemented, every Monad is also Applicative. The Monad class 现在变成了

class Applicative m => Monad m where 
    ...

A Monad 严格来说比 Applicative 更强大,任何你需要 Applicative 的地方你都可以使用 Monad 代替以下替换:

  • ap 而不是 <*>
  • return 而不是 pure
  • liftM 而不是 fmap

如果您正在编写一些新类型 SomeMonad,并且已经为 Monad class 提供了一个实例,您可以使用它来为 Applicative 提供实例] 和 Functor 也是。

import Control.Monad

instance Applicative SomeMonad where
    pure = return
    (<*>) = ap

instance Functor SomeMonad where
    fmap = liftM

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

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)

很明显,一旦您完成了该操作,就没有理由不能在传递给 for 的函数中使用任何 Parser 特定的功能,包括 Monad 内容。

另一方面,for实现者受到for接口中多态性的限制。他们必须使用 any 选择 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)

现在提供的 lambda 本身在 g 中必须是多态的(受 Applicative 约束)。 for 的调用者仍然可以灵活地选择 f,而实施者只能使用 Applicative 特性。但是 for 的调用者是函数参数的 实现者 ,所以既然该函数本身是多态的,for 的调用者就只能使用 Applicative 的特性,for 的实现者可以自由地将它与他们喜欢的任何类型一起使用(包括可能使用 monad 特性将其与其他内部值组合)。使用此特定类型签名,for 的实现者将不得不选择实例化 gfor 的调用者为 f 选择的相同类型,以便提出最终的 f (t b) return 值。但是 for 的调用者仍然会受到类型系统的限制,只能提供适用于任何 Applicative g.

的函数

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

顺便说一句,类似于等级 2 类型如何添加 "another level" 这种二元性,角色颠倒(并且等级 N 类型允许任意多个级别),也可以看到类似的二元性(再次翻转)在约束本身。再考虑签名:

for :: (Traversable t, Applicative f) => t a -> (a -> f b) -> f (t b)

for调用者在选择类型t和[时受到TraversableApplicative约束的限制=15=]。实施者可以自由使用这些约束所隐含的任何功能,而不必担心如何证明约束得到满足。