【问题标题】:What is the purpose of (<$) in the Functor class?Functor 类中 (<$) 的目的是什么?
【发布时间】:2012-12-30 02:47:20
【问题描述】:

Functor 类包含一个隐藏的第二个成员:

class Functor f where
  fmap :: (a -> b) -> f a -> f b
  (GHC.Base.<$) :: a -> f b -> f a

文档:

用相同的值替换输入中的所有位置。默认定义为fmap . const,但可能会被更高效的版本覆盖。

我想知道更多。为什么这个fmap . const 成语是一个单独的成员?替代实现如何更有效?这个组合器有什么应用?

【问题讨论】:

  • 基本上,您可能有一个更高效的用例。如果不这样做,则将其保留为默认值。这意味着你只关心结构,而不是价值。

标签: haskell functor


【解决方案1】:

它作为一个成员包含在允许用户自定义它的速度,我猜是因为它使它与&gt;&gt; 一致。

我认为在 reader monad ((-&gt;) r) 的情况下它可能会更快。

x <$ _ = const x

x <$ fa = fmap (const x) fa = (const x) . fa

虽然,这确实是编译器优化的问题。而且,它似乎没有为 base 中的 reader 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)

【讨论】:

  • 您在答案的后半部分似乎已将&lt;$ 翻转为$&gt;。错字?
  • @dbaupp:我假设是这样,所以我继续修复它。
  • @dbaupp 谢谢,我已经做到了。我喜欢强类型语言的部分原因是它们有助于捕获此类错误。
【解决方案2】:

&lt;$ 用法的另一个例子:

假设您有一个解析器函子Pparser :: P A

f &lt;$&gt; parser 表示您需要解析某些内容,然后将f 应用于结果。

a &lt;$ parser 表示你不需要解析任何东西(你对结果不感兴趣)——你只需要识别,这样可以更快。

参见例如regex-applicative 库(注意Void 构造函数的用法)。

【讨论】:

    【解决方案3】:

    下面是我目前正在编写的一些代码 sn-ps,它们可能会让您了解您将使用此组合器做什么:

    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 解析器定义更具可读性很多等等。否则你必须引入一堆额外的pures 或奇怪的括号或其他分散注意力的噪音。

    至于为什么它是类型类的一部分,向类型类添加其他多余函数的通常原因是期望某些实例能够对其进行优化,例如(&gt;&gt;)。由于效率差异取决于实例(这就是重点!)那里没有单一的答案。不过,我无法立即想到任何明显的实例会产生重大影响。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-09-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-15
      • 2014-03-06
      相关资源
      最近更新 更多