Haskell中的美元符号($)和id函数之间是否存在关系? [英] Is there a relationship between dollar sign ($) and id function in Haskell?

查看:68
本文介绍了Haskell中的美元符号($)和id函数之间是否存在关系?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这些天之一,我在阅读有关 Monad Challenge 的评论(我强烈建议以及像我这样的Haskell初学者),最后我遇到了此线程读取($)= id .

我不知道会吓到人,但是许多编程语言中的概念最好通过使人说哇"的小例子来证明.

例如,令人惊奇的是,Prolog中的append()可以从连接结果中向后"运行,以产生所有可以连接以产生它的列表.或者可以用join和fmap来定义Haskell(>> =)中的monadic绑定运算符,或者($)= id.

($)= id!?

<在Raskell/Ghci中进行尝试>

我现在明白了为什么这是真的,但还是……哇!感谢那!(...)

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

 NOTA BENE:请勿在此模块的任何地方使用($)!($)的类型是有点神奇(它可以返回未提升的类型),并且已连接.但是,它在此模块中也是* defined *,具有非魔术类型.如果您尝试在其中使用($),GHC将会非常混乱(并且*挂起*)模块,因为它在不同情况下具有不同的类型.通常这不是问题,因为连接的类型($)不是写入接口文件,因此导入文件不会引起混淆.问题仅在此处使用($)时.所以不要!

它们的实现是:

 -|身份功能.id :: a->一种id x = x-|应用操作员.{-#INLINE($)#-}($)::(a-> b)->a->bf $ x = f x 

我尝试在GHCi上彼此交换,而我得到的只是类型错误(正如我预期的那样).现在,我的疑问比开始时要多:

  1. ($)= id 是什么意思?
  2. 在哪种情况下该说法是正确的?这是否意味着我可以使用一个而不是另一个?
  3. base 中,说($)是神奇的魔法(它可以返回未提升的类型)"和被连接"是什么意思?
  4. 那在不同情况下的不同类型"呢?我认为,因为Haskell是一种强类型语言,所以一旦定义类型签名,该签名将一直保留到时间结束.那不是真的吗在某些情况下可以更改函数的类型吗?

解决方案

  1. ($)= id 是什么意思?

函数($)可以定义为

 ($)f x = f x 

即接受一个函数和一个参数并返回应用程序的结果.等效地,由于使用了curring,我们可以将其解释为仅采用 f 并返回 x 的函数.

 ($)f = \ x->x 

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

 ($)f = f 

现在,这与身份函数定义 id y = y 相同,因此我们不妨编写

 ($)= id 

  1. 在哪种情况下该说法是正确的?这是否意味着我可以使用一个而不是另一个?

等式始终成立,但有两个警告.

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

 ($)= id 

对于任何类型的 a b

可以使隐式类型参数显式并将其写入

 ($)@ a @ b = id @(a-&b; b) 

另一个警告是,在具有更高排名的功能的情况下,($)在类型检查期间会接受一些特殊处理,而 id 则不会.这是第3点的打击.

  1. 在基础上,说($)是有点神奇(它可以返回未提升的类型)"和被连接"是什么意思?

通常,在多态函数中,例如

  id ::全部为a.a->一种 

类型变量 a 可以实例化为其他类型,但只能在某些限制下进行.例如, a 可以实例化为 Int ,但不能实例化为另一个多态类型 forall b.... .这样可以保留类型系统 predicative ,这在类型推断期间会大有帮助.

通常,这不是日常编程中的问题.但是,某些函数使用等级2类型,例如 runST

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

如果我们写

  runST(此处有一些多态值) 

类型系统可以进行类型检查.但是,如果我们使用惯用语

  runST $这里有一些多态值 

然后类型推断引擎必须采用($)

的类型

 ($)::全部a b.(a-> b)->a->b 

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

由于这种习惯用法太普遍了,GHC开发人员决定在($)的键入中添加特殊情况以允许这种情况.由于这是临时的,因此,如果定义自己的($)(可能使用其他名称),然后尝试键入check runST $ ... ,将失败,因为它不使用特殊情况.

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


(我将把这部分留在这里,但是lisyarus已经更准确地介绍了它.)

  f $ x = f x-  IE.($)f x = f x-  IE.($)f x = id f x-收缩($)f = id f-收缩($)= id 

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

 ($)::(a-> b)->a->b-最好读成($)::(a-> b)->(a-> b)-形式为(c-> c),其中c =(a-> b) 

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

那在不同情况下的不同类型"呢?我认为,因为Haskell是一种强类型语言,所以一旦定义类型签名,该签名将一直保留到时间结束.那不是真的吗在某些情况下可以更改函数的类型吗?

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

One of these days I was reading comments on the Monad Challenge (which I highly recommend to any beginner in Haskell, like myself), and I ended up on this thread where I read that ($) = id.

I don't know about scaring people, but many programming languages have concepts that are best demonstrated with small examples that make people say "whoa".

For example, it's amazing that append() in Prolog can be run "backwards" from the concatenated result to yield all the lists that can be concatenated to produce it. Or that the monadic bind operator in Haskell (>>=) can be defined in terms of join and fmap, or that ($) = id.

($) = id !?

< tries it out in Raskell/Ghci >

I see now why it's true, but still... Whoah!! Thanks for that! (...)

I then checked the base-4.10.0.0 code, looking for the definitions of ($) and id, but right at the top I read this:

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!

And their implementations are:

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

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

I tried swapping one by the other on GHCi and all I got was type errors (as I expected). Now, I have more questions than what I started with:

  1. What do they mean by saying that ($) = id?
  2. In which cases is that statement true? Does that mean I can use one instead of the other?
  3. In the base, what is meant by saying that ($) is "slightly magical (it can return unlifted types)" and "being wired in"?
  4. And what about "different types in different scenarios"? I thought that since Haskell is a strongly typed language, once you define a type signature, that signature is retained until the end of Time. Is that not true? Are there cases where one can change a function's type?

解决方案

  1. What do they mean by saying that ($) = id?

The function ($) could be defined as

($) f x = f x

i.e. taking a function and an argument and returning the result of the application. Equivalently, thanks to currying, we can interpret that as taking only f and returning a function of x.

($) f = \x -> f x

Here, we can note that the function \x -> f x maps any input x to f x -- that's also what f does! So, we can simplify the definition to

($) f = f

Now, this is the same as the identity function definition id y = y, so we might as well write

($) = id

  1. In which cases is that statement true? Does that mean I can use one instead of the other?

The equation holds always, but there are two caveats.

The first one is that ($) has a more restricted type than the one of id, since ($) f = f holds only for a function f, not for any value f. This means that you can replace ($) with id, but not vice versa. When writing

($) = id

one could make the implicit type argument explicit and write that, for any types a and b

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

The other caveat is that, in the presence of higher-ranked function, ($) receives some special handling during type checking, while id does not. This is point 3 blow.

  1. In the base, what is meant by saying that ($) is "slightly magical (it can return unlifted types)" and "being wired in"?

Normally, in polymorphic functions such as

id :: forall a . a -> a

the type variable a can be instantiated to other types, but only under some restrictions. For instance, a can be instantiated to Int, but can not be instantiated to another polymorphic type forall b . .... This keeps the type system predicative, which helps greatly during type inference.

Usually, this is not an issue in everyday programming. However, some functions use rank-2 types, e.g. runST

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

If we write

runST (some polymorphic value here)

the type system can type check that. But if we use the common idiom

runST $ some polymorphic value here

then the type inference engine has to take the type of ($)

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

and choose a ~ forall s. ST s x which is polymorphic, hence forbidden.

Since this idiom is too common, the GHC developers decided to add a special case to the typing of ($) to allow this. Since this is a bit ad-hoc, if you define your own ($) (possibly under another name), and try to type check runST $ ... this will fail since it does not use the special case.

Further, a can not be instantiated to unboxed types such as Int#, or unboxed tuples (# Int#, Int# #). These are GHC extensions which allow writing functions which pass a "raw" integer, without the usual thunk wrapper. This can change the semantics, e.g. making functions stricter than they are. Unless you want to squeeze more performance from some numeric code, you can ignore these.


(I'm leaving this part here, but lisyarus already covered it more precisely.)

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

Basically, ($) is id, but with a more restrictive type. id can be used on arguments of any type, ($) instead takes a function argument.

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

Note that id :: c -> c, so the type of ($) is indeed a special case.

And what about "different types in different scenarios"? I thought that since Haskell is a strongly typed language, once you define a type signature, that signature is retained until the end of Time. Is that not true? Are there cases where one can change a function's type?

In the code of the libraries which define ($), GHC has to play some tricks to get the special typing rules to work for the other libraries and programs. For that, apparently it is required not to use ($) in said library. Unless you are developing GHC or the base module which happens to define ($), you can ignore this. It is an implementation detail internal to the compiler, not something that the user of the compiler and libraries must know.

这篇关于Haskell中的美元符号($)和id函数之间是否存在关系?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆