英文:
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
强制转换为 Int
或 Double
是有效的。这不就相当于在面向对象语言中进行向下转型吗?
难道不需要一些额外的信息(在这种情况下是一些特定于 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<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
.
答案3
得分: 0
在类型 forall a. Num a => a
† 中,forall a
和 Num a
是由“调用者”指定的参数,即定义(fifteen
)被使用的地方。类型参数在类型推断期间由 GHC 隐式填充为类型参数;Num
约束成为一个额外的参数,一个包含特定 Num
实例的函数记录((+)
、(-)
、abs
等)的“字典”,以及要传递的哪个 Num
字典是根据类型确定的。类型参数仅在编译时存在,然后字典通常会被内联以专门化函数并启用进一步的优化,因此通常这两个参数都没有运行时表示。
因此,在 fifteen :: Double
中,编译器推断出 a
必须等于 Double
,得到 (a ~ Double, Num a) => a
,首先简化为 Num Double => Double
,然后简化为简单的 Double
,因为约束 Num Double
由于存在一个 instance Num Double
定义而得到满足。这里没有子类型或运行时向下转型的情况发生,只有静态地解决相等性约束的过程。
类型参数也可以使用 fifteen @Double
的 TypeApplications
语法显式指定,通常类似于 OO 语言中的 fifteen<Double>
。
fifteen
的推断类型包括一个 Num
约束,因为字面量 15
隐式地是类似于 fromInteger (15 :: Integer)
的调用。fromInteger
的类型为 Num a => Integer -> a
,是 Num
类型类的一个方法,因此可以将字面量视为“部分应用” Integer
参数,同时保留 Num a
参数未指定,然后调用者决定为 a
提供哪个具体类型,并且编译器插入一个调用 fromInteger
函数的语句,以传递该类型的 Num
字典。
† forall
量词通常是隐式的,但可以使用各种扩展显式编写,如 ExplicitForAll
、ScopedTypeVariables
和 RankNTypes
。
‡ 我说“类似于”是因为这滥用了 15 :: Integer
表示字面量 Integer
的符号,而不是再次在 fromInteger
中定义。如果这样做会产生循环定义:fromInteger 15
= fromInteger (fromInteger 15)
= fromInteger (fromInteger (fromInteger 15))
… 这种解糖过程可能是“魔法”的,因为它是语言本身的一部分,而不是在语言内定义的东西。
英文:
In the type forall a. Num a => 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) => a
, which is simplified first to Num Double => 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<Double>
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 => Integer -> 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论