3

我正在研究以下一小段代码:

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

并且我也可以forMfor. 在进行上述更改之前,我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.

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

4

2 回答 2

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东西。

另一方面,的实现者受到 接口中的多态性的限制。他们必须与任何选择一起工作,因此他们只能使用他们编写的代码中的接口来实现。但这只会限制自身的代码,而不是传递给它的函数。forforfApplicativeforfor

如果作者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将不得不选择g使用与调用者相同的类型来实例化for选择f,以便得出最终的f (t b)返回值。但是 的调用者for仍然会受到类型系统的限制,只能提供适用于 any 的函数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)

调用者在选择和类型时forTraversableApplicative约束。实现者可以自由使用这些约束所隐含的任何功能,而不必担心如何证明约束得到满足。tf

于 2016-12-14T23:23:35.307 回答
5

第一个简短的答案是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'


第二个简短的答案是 aMonad比 a 更强大Applicative,任何你需要的地方都Applicative可以使用 aMonad代替。自从Monad-Applicative提案实施以来,每一个 Monad也是Applicative. 这个Monad类现在看起来像

class Applicative m => Monad m where 
    ...

AMonad严格来说比 a 更强大Applicative,在任何需要 an 的地方都Applicative可以使用 aMonad来代替,并进行以下替换:

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

如果您正在编写一些新类型,SomeMonad并且已经为Monad该类提供了一个实例,那么您也可以使用它来为Applicativeand提供实例Functor

import Control.Monad

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

instance Functor SomeMonad where
    fmap = liftM
于 2016-12-14T19:30:32.610 回答