我正在研究以下一小段代码:
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;继续。
答案 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
,所需要的只是您确实提供了这些输入。事实上,您几乎拥有来使用特定于Applicative
和a -> f b
的功能。多态签名的实现者没有获得这种自由,他们受到多态性的限制,只能处理任何可能的选择。
顺便说一句,类似于排名2类型如何将这种二元性的“另一个级别”与角色颠倒相加(并且排名N类型允许任意多个级别),在约束中也看到类似的二元性(再次翻转)他们自己。签名再次考虑:
a
选择b
和for :: (Traversable t, Applicative f) => t a -> (a -> f b) -> f (t b)
类型时,for
的来电受Traversable
和Applicative
限制条件的限制。实现者可以自由使用这些约束所隐含的任何函数,而无需担心如何证明约束条件得到满足。
答案 1 :(得分: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'
对的类型)
第二个简短的回答是,Monad
比Applicative
更强大,在您需要Applicative
的任何地方,您都可以使用Monad
。自Monad
-Applicative
proposal实施以来,每个 Monad
也是Applicative
。 Monad
类现在看起来像
class Applicative m => Monad m where
...
Monad
比Applicative
更强大,只要您需要Applicative
,就可以使用Monad
代替以下替换:
如果您正在编写一些新类型SomeMonad
,并为Monad
类提供了一个实例,则可以使用它来为Applicative
和{{1}提供实例也是。
Functor