该Functor
类包含一个隐藏的第二构件:
class Functor f where
fmap :: (a -> b) -> f a -> f b
(GHC.Base.<$) :: a -> f b -> f a
文档:
替换输入的所有位置具有相同值。默认定义是
fmap . const
,但这可能会被更高效的版本覆盖。
我想知道更多。为什么这个fmap . const
成语是一个单独的成员?替代实施如何更有效?这个组合器的应用是什么?
该Functor
类包含一个隐藏的第二构件:
class Functor f where
fmap :: (a -> b) -> f a -> f b
(GHC.Base.<$) :: a -> f b -> f a
文档:
替换输入的所有位置具有相同值。默认定义是
fmap . const
,但这可能会被更高效的版本覆盖。
我想知道更多。为什么这个fmap . const
成语是一个单独的成员?替代实施如何更有效?这个组合器的应用是什么?
它包括作为成员,允许用户自定义为速度,我猜是因为它使得它与>>
一致。
我认为在阅读器monad ((->) r)
的情况下可能会更快。
x <$ _ = const x
VS
x <$ fa = fmap (const x) fa = (const x) . fa
虽然,这的确是编译器优化的问题。而且,它似乎没有为读者monad定义基地。
它也可能导致严格收藏中的性能提升。即
data Strict a = Strict !a
instance Functor Strict where
fmap f (Strict a) = Strict (f a)
x <$ _ = Strict x
这种不服从法律函子,但尽管如此,你可能会想这样做在某些情况下。
第三个例子来自无限集合。考虑无限列表
data Long a = Cons a (Long a)
instance Functor Long where
fmap f (Cons x xs) = Cons (f x) (fmap f xs)
,工作正常,但想想
countUpFrom x = Cons x (countUpFrom (x+1))
ones = 1 <$ (countUpFrom 0)
现在,我们的定义,将扩大到
ones = 1 <$ (countUpFrom 0)
= fmap (const 1) (countUpFrom 0)
= Cons (const 1 0) (fmap (const 1) (countUpFrom 1)
= Cons (const 1 0) (Cons (const 1 1) (fmap (const 1) (countUpFrom 2))
也就是说,它会分配一大堆Cons
单元格,当你走这个列表。同时,在另一方面,如果你定义
x <$ _ = let xs = Cons x xs in xs
比
ones = 1 <$ countUpFrom 0
= let xs = Cons 1 xs in xs
已经喜结连理。一个更极端的例子是具有无限的树木
data ITree a = ITree a (ITree a) (ITree a)
你似乎在答案的后半部分翻转了'<$' to '$>'。错字? – huon
@dbaupp:我是这么认为的,所以我只是继续修复它。 –
@dbaupp谢谢,我已经做到了。我喜欢强类型语言的部分原因是它们有助于捕捉那样的错误。 –
下面是一些一对夫妇的代码片段,我目前正在写,可能给你你想要的使用这个组合子一个想法:
pPrimType = choice
[ WIPrimIntType <$> flag "unsigned" <*> pIntTypeSize
, WIPrimFloatType <$> flag "unrestricted" <*> pFloatTypeSize
, WIPrimBoolType <$ "boolean"
, WIPrimByteType <$ "byte"
, WIPrimOctetType <$ "octet"
]
pConst = WIConst
<$ "const"
<*> pConstType
<*> pIdent
<* "="
<*> pConstValue
<* semicolon
如果字符串文字看起来很怪异,那是因为我有OverloadedStrings
启用,这些都被转换成同时做一些其他的东西相匹配的字符串解析器(吃的空白,检查令牌的边界,&角)
看来很琐碎,但老实说这让Applicative
-y解析器定义一个批次当您使用不会产生您关心的值的分析程序(如必需的关键字等)时更具可读性。否则,你必须引入一些额外的pure
或古怪的括号或其他干扰噪音。
至于为什么它的类型的类的一部分,通常的原因用于添加否则多余功能的类型的类是期望某些情况下将能够优化它,例如(>>)
。由于效率差异取决于实例(这就是整个问题!),那里没有单一的答案。尽管如此,我不能立即想到任何明显的例子,它们将会产生重大差异。
<$
用法又如:
假设你有一个解析器函子P
和parser :: P A
。
f <$> parser
意味着你需要分析的东西,然后应用到f
的结果。
a <$ parser
意味着您不需要解析任何东西(您对结果不感兴趣) - 您只需要即可识别,这可以更快。
参见例如regex-applicative库(请注意使用Void
构造函数)。
基本上,你可能会有一个更有效的用例。如果你不这样做,你就默认它。这意味着你只关心结构,而不关心价值。 – PyRulez