类型规定操作符类似于面向对象语言中的向下转型?

huangapple go评论65阅读模式
英文:

The type-specification operator is like down-casting in Object-Oriented languages?

问题

以下是您要翻译的代码部分:

Prelude> fifteen = 15
Prelude> :t fifteen
fifteen :: Num a => a
Prelude> fifteenInt = fifteen :: Int
Prelude> fifteenDouble = fifteen :: Double
Prelude> :t fifteenInt
fifteenInt :: Int
Prelude> :t fifteenDouble
fifteenInt :: Double

在这里,Num 是一种类型类,类似于面向对象语言中的基类。我的意思是,当我编写一个多态函数时,我会使用受 Num 类型类约束的 类型变量。然而,正如上面所示,将 fifteen 强制转换为 IntDouble 是有效的。这不就相当于在面向对象语言中进行向下转型吗?

难道不需要一些额外的信息(在这种情况下是一些特定于 Double 类型的函数)才能够这样做吗?

感谢您的帮助。

英文:

I was going through the book Haskell Programming from First Principles and came across following code-snippet.

Prelude> fifteen = 15
Prelude> :t fifteen
fifteen :: Num a => a
Prelude> fifteenInt = fifteen :: Int
Prelude> fifteenDouble = fifteen :: Double
Prelude> :t fifteenInt
fifteenInt :: Int
Prelude> :t fifteenDouble
fifteenInt :: Double

Here, Num is the type-class that is like the base class in OO languages. What I mean is when I write a polymorphic function, I take a type variable that is constrained by Num type class. However, as seen above, casting fifteen as Int or Double works. Isn't it equivalent to down-casting in OO languages?

Wouldn't some more information (a bunch of Double type specific functions in this case) be required for me to be able to do that?

Thanks for helping me out.

答案1

得分: 9

No, it's not equivalent. Downcasting in OO is a runtime operation: you have a value whose concrete type you don't know, and you basically assert that it has some particular case – which is an error if it happens to be actually a different concrete type.

在面向对象编程中,不,它并不等价。向下转型是一个_运行时_操作:你有一个具体类型未知的值,基本上是断言它具有某个特定的情况 - 如果它实际上是不同的具体类型,那么这就是一个错误。

In Haskell, :: isn't really an operator at all. It just adds extra information to the typechecker at compile-time. I.e. if it compiles at all, you can always be sure that it will actually work at runtime.

在Haskell中,::实际上根本不是运算符。它只是在_编译时_向类型检查器添加额外的信息。也就是说,如果它能够编译通过,你始终可以确保它在运行时实际上可以工作。

The reason it works at all is that fifteen has no concrete type. It's like a template / generic in OO languages. So when you add the :: Double constraint, the compiler can then pick what type is instantiated for a. And Double is ok because it is a member of the Num typeclass, but don't confuse a typeclass with an OO class: an OO class specifies one concrete type, which may however have subtypes. In Haskell, subtypes don't exist, and a class is more like an interface in OO languages. You can also think of a typeclass as a set of types, and fifteen has potentially all of the types in the Num class; which one of these is actually used can be chosen with a signature.

它能够工作的原因是fifteen 没有 具体的类型。它就像OO语言中的_模板_ / 泛型。因此,当你添加:: Double约束时,编译器可以_选择_ 为a 实例化 什么类型。而且Double是可以的,因为它是Num 类型类 的成员,但不要混淆类型类与OO类:OO类指定一个具体的类型,但可以有_子类型_。在Haskell中,子类型不存在,而且类更像是OO语言中的_接口_。你还可以将类型类看作是一组类型,fifteen 可能具有Num类中的所有类型;哪一个实际上被_使用_ 可以通过签名选择。

英文:

No, it's not equivalent. Downcasting in OO is a runtime operation: you have a value whose concrete type you don't know, and you basically assert that it has some particular case – which is an error if it happens to be actually a different concrete type.

In Haskell, :: isn't really an operator at all. It just adds extra information to the typechecker at compile-time. I.e. if it compiles at all, you can always be sure that it will actually work at runtime.

The reason it works at all is that fifteen has no concrete type. It's like a template / generic in OO languages. So when you add the :: Double constraint, the compiler can then pick what type is instantiated for a. And Double is ok because it is a member of the Num typeclass, but don't confuse a typeclass with an OO class: an OO class specifies one concrete type, which may however have subtypes. In Haskell, subtypes don't exist, and a class is more like an interface in OO languages. You can also think of a typeclass as a set of types, and fifteen has potentially all of the types in the Num class; which one of these is actually used can be chosen with a signature.

答案2

得分: 4

Downcasting is not a good analogy. Rather, compare to generic functions.

Very roughly, you can pretend that your fifteen is a generic function

// pseudo code in OOP
A fifteen<A>() where A : Num

When you use fifteen :: Double in Haskell, you tell the compiler that the result of the above function is Double, and that enables the compiler to "call" the above OOP function as fifteen<Double>(), inferring the generic argument.

With some extension on, GHC Haskell has a more direct way to choose the generic parameter, namely the type application fifteen @Double.

There is a difference between the two ways in that ... :: Double specifies what is the return type, while @Double specifies what is the generic argument. In this fifteen case they are the same, but this is not always the case. For instance:

> list = [(15, True)]
> :t list
list :: Num a => [(a, Bool)]

Here, to choose a = Double, we need to write either list :: [(Double, Bool)] or list @Double.

英文:

Downcasting is not a good analogy. Rather, compare to generic functions.

Very roughly, you can pretend that your fifteen is a generic function

<!-- language: none -->

// pseudo code in OOP
A fifteen&lt;A&gt;()  where A : Num

When you use fifteen :: Double in Haskell, you tell the compiler that the result of the above function is Double, and that enables the compiler to "call" the above OOP function as fifteen&lt;Double&gt;(), inferring the generic argument.

With some extension on, GHC Haskell has a more direct way to choose the generic parameter, namely the type application fifteen @Double.

There is a difference between the two ways in that ... :: Double specifies what is the return type, while @Double specifies what is the generic argument. In this fifteen case they are the same, but this is not always the case. For instance:

&gt; list = [(15, True)]
&gt; :t list
list :: Num a =&gt; [(a, Bool)]

Here, to choose a = Double, we need to write either list :: [(Double, Bool)] or list @Double.

答案3

得分: 0

在类型 forall a. Num a =&gt; a† 中,forall aNum a 是由“调用者”指定的参数,即定义(fifteen)被使用的地方。类型参数在类型推断期间由 GHC 隐式填充为类型参数;Num 约束成为一个额外的参数,一个包含特定 Num 实例的函数记录((+)(-)abs 等)的“字典”,以及要传递的哪个 Num 字典是根据类型确定的。类型参数仅在编译时存在,然后字典通常会被内联以专门化函数并启用进一步的优化,因此通常这两个参数都没有运行时表示。

因此,在 fifteen :: Double 中,编译器推断出 a 必须等于 Double,得到 (a ~ Double, Num a) =&gt; a,首先简化为 Num Double =&gt; Double,然后简化为简单的 Double,因为约束 Num Double 由于存在一个 instance Num Double 定义而得到满足。这里没有子类型或运行时向下转型的情况发生,只有静态地解决相等性约束的过程。

类型参数也可以使用 fifteen @DoubleTypeApplications 语法显式指定,通常类似于 OO 语言中的 fifteen&lt;Double&gt;

fifteen 的推断类型包括一个 Num 约束,因为字面量 15 隐式地是类似于 fromInteger (15 :: Integer) 的调用。fromInteger 的类型为 Num a =&gt; Integer -&gt; a,是 Num 类型类的一个方法,因此可以将字面量视为“部分应用” Integer 参数,同时保留 Num a 参数未指定,然后调用者决定为 a 提供哪个具体类型,并且编译器插入一个调用 fromInteger 函数的语句,以传递该类型的 Num 字典。

forall 量词通常是隐式的,但可以使用各种扩展显式编写,如 ExplicitForAllScopedTypeVariablesRankNTypes

‡ 我说“类似于”是因为这滥用了 15 :: Integer 表示字面量 Integer 的符号,而不是再次在 fromInteger 中定义。如果这样做会产生循环定义:fromInteger 15 = fromInteger (fromInteger 15) = fromInteger (fromInteger (fromInteger 15))… 这种解糖过程可能是“魔法”的,因为它是语言本身的一部分,而不是在语言内定义的东西。

英文:

In the type forall a. Num a =&gt; a†, the forall a and Num a are parameters specified by the “caller”, that is, the place where the definition (fifteen) used. The type parameter is implicitly filled in with a type argument by GHC during type inference; the Num constraint becomes an extra parameter, a “dictionary” comprising a record of functions ((+), (-), abs, &c.) for a particular Num instance, and which Num dictionary to pass in is determined from the type. The type argument exists only at compile time, and the dictionary is then typically inlined to specialise the function and enable further optimisations, so neither of these parameters typically has any runtime representation.

So in fifteen :: Double, the compiler deduces that a must be equal to Double, giving (a ~ Double, Num a) =&gt; a, which is simplified first to Num Double =&gt; Double, then to simply Double, because the constraint Num Double is satisfied by the existence of an instance Num Double definition. There is no subtyping or runtime downcasting going on, only the solution of equality constraints, statically.

The type argument can also be specified explicitly with the TypeApplications syntax of fifteen @Double, typically written like fifteen&lt;Double&gt; in OO languages.

The inferred type of fifteen includes a Num constraint because the literal 15 is implicitly a call to something like fromInteger (15 :: Integer)‡. fromInteger has the type Num a =&gt; Integer -&gt; a and is a method of the Num typeclass, so you can think of a literal as “partially applying” the Integer argument while leaving the Num a argument unspecified, then the caller decides which concrete type to supply for a, and the compiler inserts a call to the fromInteger function in the Num dictionary passed in for that type.

forall quantifiers are typically implicit, but can be written explicitly with various extensions, such as ExplicitForAll, ScopedTypeVariables, and RankNTypes.

‡ I say “something like” because this abuses the notation 15 :: Integer to denote a literal Integer, not circularly defined in terms of fromInteger again. (Else it would loop: fromInteger 15 = fromInteger (fromInteger 15) = fromInteger (fromInteger (fromInteger 15))…) This desugaring can be “magic” because it’s a part of the language itself, not something defined within the language.

huangapple
  • 本文由 发表于 2020年1月3日 22:42:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/59580509.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定