是否可以在Haskell中使用可调用对象定义类型?

时间:2011-10-26 12:25:35

标签: haskell

我对haskell很新,对它的类型系统还不太满意。我想知道,如果有能力定义类型(数据类型?),哪些实例可以作为函数调用?

类似物

__call__ 
Python中的

方法或类方法

operator() 

在c ++中。 (更多示例在维基百科中给出了单词" Function object")。

这种构造的应用实例是Polynom。该对象由其系数列表定义,即我希望有这样的类型:

data Num a => Polynom a = Polynom [a]
                          deriving (...)

现在我可以定义功能

callPoly :: Num a => (Polynom a) -> a -> a
callPoly p x = ... -- implementation: extract coefficients of p,
                   -- construct polynomial and call it on x

(这里我不打扰,能够在Floats上用Int系数调用多项式......它只是技术细节)

现在我可以在我的polinoomial上调用它(在交互式提示符中):

let myPoly = Polynomial [1 2 3]
let applicationResult = callPoly myPoly 3

但这种方式并不太花哨。希望能够直接调用多项式作为

let applicationResult = myPoly 3

所以问题是:可以定义这样的多项式类型,可以调用哪些对象(实例)(用作函数)?可能是这种模式可能以其他方式实施,不涉及数据&#39 ;?可能是一些玩功能类型或smth。别的?

当然,这个想法不仅可以应用于多项式。实际上,对于任何必须"具有类型"并且有一些附加数据" (在多项式的情况下 - 它是系数)。

或者,如果这是不可能的,那么是否存在某些特定原因或者它不被支持?

P.S。:在我看来,直接方法(如上所述)是不可能的,因为要可调用myPoly必须是类型(Int - > Int)。但是类型(Int - > Int)不能附加任何数据(即多项式系数)。但我想确保,我是对的。

3 个答案:

答案 0 :(得分:7)

熟悉C ++“函数对象”概念是件好事,因为这是对Haskell关于使用普通旧函数可以做什么的想法的一个很好的介绍......具体来说,currying,部分应用和传递函数作为参数其他功能。

在C ++中,您的代码看起来像:

class Polynomial {
  int coefficients[];
public:
  Polynomial(int coefficients[]) { /* ... */ }
  int operator() (int value) { /* ... */ }
};

int coefficients[] = {1, 2, 3};
Polynomial(coefficients)(4); // note the syntax here!

这基本上表达了一个单一的纯函数:一个多项式求值器,它取一个系数列表和一个值。它可以很容易地在C ++中表达为:

int evaluatePolynomial(int coefficients[], int value);

int coefficients[] = {1, 2, 3};
evaluatePolynomial(coefficients, 4);

但是这个表格不像前一个表格那样 curried 。关于咖喱形式的好处是你可以说:

Polynomial p = Polynomial(coefficients);
p(4);
p(5);
p(6);

而不是:

evaluatePolynomial(coefficients, 4);
evaluatePolynomial(coefficients, 5);
evaluatePolynomial(coefficients, 6);

好。所以我们把这个“函数对象”的东西想象成一个面向对象的编程概念 - 一个伪装成函数的对象 - 但现在让我们忘记对象。如果你在Haskell中看一下它,它只是一个函数,不需要任何用户定义的数据类型来表达:

polynomial :: Num a => [a] -> a -> a

您可以“正常”调用它(与上面的evaluatePolynomial()一样),立即将它应用于两个参数:

polynomial [1, 2, 3] 4

但由于Haskell函数是curry,你可以部分应用(与Polynomial函数对象一样):

do
  let p = polynomial [1, 2, 3]
  print (p 4)
  print (p 5)
  print (p 6)

容易腻。现在,如果你想更接近C ++,你有一个特定的数据类型代表你的Polynomial函数对象,你可以这样做......

newtype Polynomial a = P (a -> a)  -- function object
mkPolynomial :: Num a => [a] -> Polynomial a  -- constructor

......但这种额外的复杂性并没有带来任何好处。您立即注意到Polynomial没有什么特别之处,它只包含一个常规函数,因此您最终只需要再次打开它,如:

do
  let (P p) = mkPolynomial [1, 2, 3]
  print (p 4)
  print (p 5)
  print (p 6)

简而言之,您在思考问题的过程中,只是在功能方面,而不是在对象方面,您的Haskell代码最终会变得更简单,更惯用。

答案 1 :(得分:4)

我可能会在这里弄错我的术语,因为我自己不是Haskell的老手。但是,根据我对Haskell的理解:

由于Haskell不是面向对象的,因此它没有对象或实例(在传统意义上,就是这样)。您可以使用数据类型的值来代替数据类型的实例。话虽这么说,因为函数就像整数和字符串一样是数据(值),你可以拥有可以调用的值,因为它们可以携带自己的上下文(就像OO世界中的实例一样)。

如果您的目标是传递带有PolyNom a的值,则可以部分评估您的函数callPoly,然后将其视为您的“可赎回的PolyNom“。例如:

myPoly = PolyNom [1, 2, 3]
callMyPoly = callPoly myPoly

-- or simply

callMyPoly = callPoly (PolyNom [1, 2, 3])

现在,callMyPoly的类型是:

callMyPoly :: Num a => a -> a

你可以这样称呼它:

callMyPoly 5

相当于:

callPoly myPoly 5

答案 2 :(得分:4)

是的,在Haskell中你不能拥有可调用的对象,因为这会使类型推断陷入困境。您必须为函数指定明确的名称,就像在不支持__call__且需要显式方法名称的OO语言中一样。

另一方面,部分函数应用和闭包使得从多项式表示中获得多项式函数变得非常容易,因此限制并不是那么糟糕。

let polyF = callPoly myPoly in
(polyF 17) + (polyF 42)