例如,为什么不能定义一个函数-
fun::a
fun = 1
-- OR
someInt::Int
someInt = 3
fun::Num a => a
fun = someInt
这在Java中似乎是可能的-
class App {
public static void main(String[] args) {
System.out.println(new SomeClass().hello().sayHi());
}
}
class SomeClass {
Hi hello() {
A a = new A();
return a;
}
}
interface Hi {
String sayHi();
}
class A implements Hi {
public String sayHi() {
return "Hi from A";
}
}
class B implements Hi {
public String sayHi() {
return "Hi from B";
}
}
但 haskell 的等价物不起作用
main = print $ sayHi hello
hello:: Hi a => a
hello = A
class Hi a where
sayHi::a -> String
data A = A
data B = B
instance Hi A where
sayHi _ = "Hi from A"
instance Hi B where
sayHi _ = "Hi from B"
我试图理解Java中的接口和Haskell中的类型类之间的根本区别?每种方法的限制和好处是什么?
3条答案
按热度按时间txu3uszq1#
具有以下功能:
你向函数的用户承诺,user 可以选择他们想要的 * 任何 *
a
,只要它是Hi a
的一个示例用途:但这并不是您的实现所显示的:它总是返回一个
A
对象。在这个意义上,面向对象编程(Java)和函数式编程(Haskell)中的类型约束在某种程度上是彼此的“反向”。在Java中,你承诺它将是Hi
的子类,在Haskell中,你承诺它可以返回Hi
类型类的任何类型的值。因此,
hello
简单地表示为:如果您在一个函数中使用
hello
,该函数承诺它可以处理任何Hi a => a
项,例如:因此,它将使
a
与A
统一。eyh26e7m2#
这是一个很常见的误解。
多态在Haskell中的工作方式与OO语言完全不同(尽管许多OO语言都有模板/泛型特性,这与Haskell的多态更相似)。
在Java中,多态性基于 * 子类型化 *:通过定义一个接口
I
,你定义了一个(大的,抽象的)类型,一个示例化这个接口的类定义了I
的一个子类型,所以,一个Java接口和一个Java类并没有本质上的不同--事实上,在C++中接口和类使用相同的class
关键字;Java只是对如何使用每一种语言进行了一些合理的限制。这意味着多态函数实际上不需要做普通函数以外的任何事情:它们只有一个引用类型
I
的签名,类型I
包含任何子类型,所以如果I
被认为是结果类型,实现者可以自由返回任何子类型。在Haskell中,没有子类型(尽管有方法模拟它们),类型类根本不定义类型(而是 * 类型集 *;这个区别很重要,原因和区分整数和整数列表很重要一样,所以它们不能像类型一样出现在签名中,Haskell多态函数的工作方式确实与单态函数完全不同,签名实际上非常清楚地表达了这一点:
表示
fun
首先接受一个(隐式、类型级)参数Num a
,然后生成一个a
类型的值。......事实上,还有更多的事情正在发生:上述签名的完整格式为
基本上它需要两个参数
a
Num a
,该字典解释了如何将a
用作数字类型a
类型的值,也就是说,* 确切的类型 *a
,而不是某个子类型或其他类型。当调用
fun
时,你不需要显式传递这两个参数中的任何一个,编译器可以为你做这件事,但是它需要明确它应该是什么类型,你可以显式传递类型参数,这就是type applications syntax的作用:...不要与
(fun :: Int)
混淆,尽管在本例中它具有完全相同的效果:这里,编译器从上下文推断a
必须是Int
类型,因为这是预期的结果,而fun @Int
显式指示编译器使用Int
,并且不允许它适应环境。这种实现多态性的方法给了调用者很大的能力,但是当然,另一方面是被调用者需要通过支持调用者可能请求的所有类型来支持所有的通用性。
这通常不是一个大问题,只是不要试图把Haskell代码塞进你在Java这样的语言中的思维模式中。Java中的类或接口通常最好对应于一个简单的
data
结构,或者实际上只是一个已经存在的类型的值,而不是任何类型类。对于您的示例,为什么不直接用途:
我在这里所做的基本上只是将
A
和B
声明为类似于Hi
类型的子类型--不是首先抽象地定义Hi
,然后事后用子类型填充它,而是预先声明Hi
由A
和B
组成。或者,如果你想保持
Hi
open,也就是说,想让下游的人以不可预见的方式定制它...那么,你得到什么类型的字符串也是完全不可预见的,那么拥有类型级抽象的意义何在呢?你可以直接把字符串作为参数,这会使事情变得更简单:[2]这里要准确地说:在Haskell中,*Rank-0类型 * 没有子类型。Rank-0类型也可以被描述为具体类型,而更高级别的类型中有quantor。更高级别的类型确实有子类型(基本上,通过在不同的类上量化)。但这有点回避问题,因为Rank〉0类型已经内置了多态性。
hlswsv353#
我想我会将您的Java代码翻译如下:
但这不是惯用的代码,您可能不需要经常编写这样的代码。