【问题标题】:Is it possible to define type with callable objects in Haskell?是否可以在 Haskell 中使用可调用对象定义类型?
【发布时间】:2011-12-15 16:41:00
【问题描述】:

我对 haskell 很陌生,对它的类型系统还不太熟悉。我想知道,如果有能力定义类型(数据类型?),哪些实例可以被称为函数?

类比是

__call__ 

Python 中的方法或类方法

operator() 

在 C++ 中。 (维基百科中给出了“函数对象”一词的更多示例)。

这种构造的应用示例是 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

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

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

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

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

let applicationResult = myPoly 3

那么问题来了:可以定义这样的Polynomial类型,哪些对象(实例)可以被调用(作为函数使用)?可能这种模式可以以其他方式实现,而不涉及“数据”?可能是一些玩函数类型或smth。还有吗?

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

或者如果这是不可能的,那么是有什么具体原因还是不支持?

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

【问题讨论】:

  • let myPoly = callPoly (Polynomial [1, 2, 3]) 有什么问题?
  • 为什么不发明自己的多项式应用程序“语法”,例如[] ||| x = 0 ; (c :cs) ||| x = c + x * (cs ||| x) 然后在ghci 你问[1,2,3,4,5] ||| 3 并得到547 作为答案。
  • @shang 谢谢。最新链接正是我想要的。抱歉,我自己没有找到。

标签: haskell


【解决方案1】:

很高兴您熟悉 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 函数是柯里化的,所以可以部分应用(与 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 代码最终会变得越简单、越惯用。

【讨论】:

  • 感谢本教程。这个想法很好。但我仍然以 OO 风格思考。对于您的解决方案,我不明白如何定义其他操作(添加、区分等),而这些操作应该是多态的。例如。我可以有样条线,它们应该是可调用的,并且有加法和微分等。我不明白,如何在这种情况下采用你的解决方案。问题是在 p 中我们丢失了关于系数的信息(在我的理解中应该被封装到多项式对象中)。
  • 好吧,如果你愿意,你可以定义newtype Polynomial a = Num a => P { coefficients :: [a] }和evaluatePolynomial :: Num a => Polynomial a -> a -> a。这与您的原始定义基本相同。但它也和我的一样,只是加了一点额外的糖。你仍然可以说let p = evaluatePolynomial (P [1, 2, 3]) 或其他。
  • 这仍然有同样的问题(我试图在这个问题中解决):值,它有关于系数和值的信息,它是函数是不同的(它们绑定到不同的变量)。但我希望,那些是一样的。在这种情况下,最好(恕我直言)用像(|||) :: (a Int) -> Int -> Int 这样的中缀操作来定义class Callable a ...,并为多项式实现这个操作。然后将其称为let y = myPoly ||| x(此解决方案已在其他答案中提出)。无论如何,感谢您的 cmets。
  • 您建议的Callable 类已作为Category 存在。还有一种方便的数据类型,可以很好地模拟Category:(->)。您的中缀(|||) 操作与我上面的polynomial 函数相同。也就是说,我可以称它为(|||),因此可以写成[1, 2, 3] ||| 4。关键是,解决方案是相同的;我试图帮助您摆脱只会使问题复杂化的 OO 思维。
【解决方案2】:

我可能在这里弄错了我的术语,因为我自己不是 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

【讨论】:

  • callMyPoly 的类型只有在单态不适用的情况下才是多态的。如果您将callMyPoly 定义为给定,则应用 MR(如果未根据 -XNoMonomorphismrestriction 或 pragma 禁用)。要获得多态类型,1) 给出类型签名 2) 与参数绑定,callMyPoly x = callPoly myPoly x 或 3) 禁用 MR。
【解决方案3】:

是的,在 Haskell 中你不能有可调用的对象,因为这会搞乱类型推断。您必须为您的函数提供明确的名称,就像在不支持 __call__ 并且需要明确的方法名称的 OO 语言中一样。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-30
    • 1970-01-01
    • 1970-01-01
    • 2021-03-15
    • 1970-01-01
    • 2015-12-16
    • 2011-04-09
    • 2021-09-28
    相关资源
    最近更新 更多