首页 文章

Haskell中的美元符号($)和id函数之间是否存在关系?

提问于
浏览
17

有一天我正在阅读关于Monad Challenge的评论(我强烈推荐给Haskell的任何初学者,就像我一样),最后我在this thread读到 ($) = id .

我不知道如何吓唬人,但是许多编程语言的概念最好用小例子来说明,这些例子让人们说“哇” . 例如,令人惊讶的是,Prolog中的append()可以从连接结果“向后”运行,以产生可以连接以生成它的所有列表 . 或者Haskell(>> =)中的monadic绑定运算符可以用join和fmap或($)= id来定义 . ($)= id!? <在Raskell / Ghci尝试>我现在看到为什么它是真的,但仍然......哇!感谢那! (......)

然后我检查了base-4.10.0.0代码,寻找 ($)id 的定义,但是在顶部我读到了这个:

NOTA BENE: Do NOT use ($) anywhere in this module! The type of ($) is
slightly magical (it can return unlifted types), and it is wired in.
But, it is also *defined* in this module, with a non-magical type.
GHC gets terribly confused (and *hangs*) if you try to use ($) in this
module, because it has different types in different scenarios.

This is not a problem in general, because the type ($), being wired in, is not
written out to the interface file, so importing files don't get confused.
The problem is only if ($) is used here. So don't!

他们的实现是:

-- | Identity function.
id                      :: a -> a
id x                    =  x

-- | Application operator.
{-# INLINE ($) #-}
($)                     :: (a -> b) -> a -> b
f $ x                   =  f x

我尝试在GHCi上逐个交换,我得到的只是类型错误(正如我所料) . 现在,我的问题比我开始时的问题多:

  • 这是什么意思 ($) = id

  • 在哪种情况下该陈述是真的?这是否意味着我可以使用一个而不是另一个?

  • base 中, ($) 是"slightly magical (it can return unlifted types)"和"being wired in"是什么意思?

  • 那么"different types in different scenarios"怎么样?我认为,由于Haskell是一种强类型语言,因此一旦定义了类型签名,该签名将保留到时间结束 . 这不是真的吗?是否有人可以改变功能的类型?

2 回答

  • 16

    他们说($)= id是什么意思?

    函数 ($) 可以定义为

    ($) f x = f x
    

    即获取函数和参数并返回应用程序的结果 . 同样地,由于currying,我们可以将其解释为仅采用 f 并返回 x 的函数 .

    ($) f = \x -> f x
    

    在这里,我们可以注意到函数 \x -> f x 将任何输入 x 映射到 f x - 这也是 f 的作用!所以,我们可以简化定义

    ($) f = f
    

    现在,这与身份函数定义 id y = y 相同,所以我们不妨写一下

    ($) = id
    

    在哪种情况下该陈述是真的?这是否意味着我可以使用一个而不是另一个?

    这个等式始终存在,但有两个警告 .

    第一个是 ($) 具有比 id 更受限制的类型,因为 ($) f = f 仅适用于函数 f ,而不适用于任何值 f . 这意味着您可以将 ($) 替换为 id ,但反之亦然 . 写作时

    ($) = id
    

    一个可以使隐式类型参数显式,并为任何类型 ab

    ($) @ a @ b = id @ (a->b)
    

    另一个需要注意的是,在存在更高级别的函数时, ($) 在类型检查期间会收到一些特殊处理,而 id 则不会 . 这是第3点打击 .

    在基础中,说($)是“有点神奇(它可以返回未提升的类型)”和“被连线”是什么意思?

    通常,在诸如的多态函数中

    id :: forall a . a -> a
    

    类型变量 a 可以实例化为其他类型,但仅限于某些限制 . 例如, a 可以实例化为 Int ,但不能实例化为另一个多态类型 forall b . ... . 这使类型系统保持预测性,这在类型推断期间有很大帮助 .

    通常,这不是日常编程中的问题 . 但是,某些功能使用rank-2类型,例如 runST

    runST :: (forall s. ST s x) -> x
    

    如果我们写

    runST (some polymorphic value here)
    

    类型系统可以键入检查 . 但如果我们使用常见的习语

    runST $ some polymorphic value here
    

    那么类型推理引擎必须采用 ($) 的类型

    ($) :: forall a b . (a -> b) -> a -> b
    

    并选择 a ~ forall s. ST s x 这是多态的,因此被禁止 .

    由于这个习惯用法太常见,GHC开发人员决定在 ($) 的输入中添加一个特殊情况来允许这个 . 由于这有点特别,如果您定义自己的 ($) (可能在另一个名称下),并尝试键入check runST $ ... ,这将失败,因为它不使用特殊情况 .

    此外, a 无法实例化为未装箱的类型,例如 Int# 或未装箱的元组 (# Int#, Int# #) . 这些是GHC扩展,它允许编写传递"raw"整数的函数,而不需要通常的thunk包装器 . 这可以改变语义,例如使功能比...更严格他们是 . 除非您想从某些数字代码中获得更多性能,否则您可以忽略这些 .


    (我将这部分留在这里,但是lisyarus已经更精确地覆盖了它 . )

    f $ x = f x
    -- i.e.
    ($) f x = f x
    -- i.e.
    ($) f x = id f x
    -- eta contraction
    ($) f = id f
    -- eta contraction
    ($) = id
    

    基本上, ($)id ,但具有更严格的类型 . id 可用于任何类型的参数, ($) 而是采用函数参数 .

    ($) :: (a -> b) -> a -> b
    -- is better read as
    ($) :: (a -> b) -> (a -> b)
    -- which is of the form (c -> c) with c = (a -> b)
    

    注意 id :: c -> c ,所以 ($) 的类型确实是一种特殊情况 .

    那么“不同场景中的不同类型”呢?我认为,由于Haskell是一种强类型语言,因此一旦定义了类型签名,该签名将保留到时间结束 . 这不是真的吗?是否有人可以改变功能的类型?

    在定义 ($) 的库的代码中,GHC必须发挥一些技巧才能使特殊的输入规则适用于其他库和程序 . 为此,显然不需要在所述库中使用 ($) . 除非您正在开发GHC或恰好定义 ($)base 模块,否则您可以忽略它 . 它是编译器内部的实现细节,而不是编译器和库的用户必须知道的内容 .

  • 10

    Haskell确实是强类型的;该问题与特定于 ($) 运算符的一些hackery有关 . 不幸的是,我不知道它是什么:希望有人会回答你的问题3(和问题4自动) .

    关于问题1,看看类型:

    id :: a -> a
    ($) :: (a -> b) -> (a -> b)
    

    重命名 c = a -> b 并获得 ($) :: c -> c ,这意味着 ($) 的类型是 id 类型的规范,因此至少类型允许我们使用 id 来实现 ($) .

    现在,看一下 ($) 的定义:

    f $ x = f x
    

    让我们重写一下:

    ($) f = \x -> f x
    

    并应用eta-reduction:

    ($) f = f
    

    现在可以清楚地看到 ($) 只是 id ,具有更具体的类型(因此 f 始终是一个函数) .

    请注意,它不会以另一种方式工作,因为 ($) 的类型更具限制性 . 例如,您可以调用 id 5 并获取 5 ,但 ($) 5 将不会进行类型检查: 5 没有 a -> b 形式的类型 .

    ($) 的要点是它具有非常低的优先级并且允许避免在 f 的参数周围使用括号,并且可以像

    someFunction $ whatever complex computation you dont need braces around
    

相关问题