是否可以同时具有欺骗和可变参数功能?


13

我正在考虑在动态类型的函数编程语言中同时提供currying和variadic函数,但是我想知道是否可行。

这是一些伪代码:

sum = if @args.empty then 0 else @args.head + sum @args.tail

据说应该总结所有的论点。然后,如果sum本身被视为数字,则结果为0。例如,

sum + 1

等于1,假设它+只能对数字起作用。但是,即使sum == 0是真的,sum无论给出多少个参数(因此同时“部分应用”和“变量”),它都将仍然保持其值和功能属性,例如,如果我声明

g = sum 1 2 3

g等于6,但是,我们仍然可以进一步应用g。例如,g 4 5 == 15是真的。在这种情况下,我们不能用g文字替换对象6,因为尽管它们在被视为整数时会产生相同的值,但它们内部包含不同的代码。

如果将这种设计用于真实的编程语言中,是否会引起任何混淆或歧义?


1
严格来说,使用currying作为语言的基础意味着所有函数都是一元的-不仅没有可变参数函数,甚至没有二进制函数!但是,用这种语言编写的程序看起来仍然好像有多个参数,对于可变参数函数和普通函数一样。
Kilian Foth,2015年

然后,我的问题简化为“对象可以同时是一个函数和一个非函数值吗?” 在上面的示例中,sumis 0没有参数,并使用参数递归调用自身。
曾荫权

那不是工作reduce吗?
棘手怪胎2015年

1
看看你使用的功能argsemptyhead,和tail。这些都是列表函数,这表明可能更容易,更直接的方法是使用可变参数所在的列表。(因此,sum [1, 2, 3]而不是sum 1 2 3
Michael Shaw

Answers:


6

可变参数如何实现?我们需要某种机制来表示参数列表的结尾。这可以是

  • 特殊的终止符值,或
  • 作为额外参数传递的vararg列表的长度。

这两种机制都可以在currying的上下文中使用,以实现varargs,但是正确的键入成为主要问题。假设我们正在处理一个函数sum: ...int -> int,除了该函数使用currying(所以我们实际上有一个更像的类型sum: int -> ... -> int -> int,只是我们不知道参数的数量)。

情况:终止符值:end设为特殊终止符,并T为类型sum。现在我们知道应用于end该函数的返回值是:sum: end -> int,而应用于int的返回值是另一个类似于sum的函数:sum: int -> T。因此T是以下类型的联合:T = (end -> int) | (int -> T)。通过置换T,我们得到各种可能的类型,如end -> intint -> end -> intint -> int -> end -> int,等。然而,大多数类型的系统没有适应这种类型。

案例:显式长度:vararg函数的第一个参数是varargs的数量。所以sum 0 : intsum 1 : int -> intsum 3 : int -> int -> int -> int等,这是在一些类型的系统中支持并是一个例子依赖打字。实际上,参数个数将是一个类型参数,而不是一个常规的参数-它是没有意义的功能的元数依赖于运行时的值,s = ((sum (floor (rand 3))) 1) 2显然是生病类型的:这个计算结果为s = ((sum 0) 1) 2 = (0 1) 2s = ((sum 1) 1) 2 = 1 2s = ((sum 2) 1) 2 = 3

实际上,这些技术都不应该使用,因为它们容易出错,并且在普通类型系统中没有(有意义的)类型。相反,只需将值列表作为一个参数传递:sum: [int] -> int

是的,对象有可能同时显示为函数和值,例如在带有强制的类型系统中。让sumSumObj,它有两个强制转换:

  • coerce: SumObj -> int -> SumObj允许sum用作功能,并且
  • coerce: SumObj -> int 允许我们提取结果。

从技术上讲,这是上述终止符值情况的变体,带有T = SumObj,并且coerce是该类型的解包器。在许多面向对象的语言中,这可以通过操作符重载轻松实现,例如C ++:

#include <iostream>
using namespace std;

class sum {
  int value;
public:
  explicit sum() : sum(0) {}
  explicit sum(int x) : value(x) {}
  sum operator()(int x) const { return sum(value + x); }  // function call overload
  operator int() const { return value; } // integer cast overload
};

int main() {
  int zero = sum();
  cout << "zero sum as int: " << zero << '\n';
  int someSum = sum(1)(2)(4);
  cout << "some sum as int: " << someSum << '\n';
}

很棒的答案!将varargs打包到列表中的缺点是您失去了curring的部分应用程序。我正在使用终止符方法的Python版本,使用关键字参数..., force=False)来强制应用初始函数。
ThomasH

您可以使自己的高阶函数部分地应用带有列表的函数,例如curryList : ([a] -> b) -> [a] -> [a] -> b, curryList f xs ys = f (xs ++ ys)
杰克

2

您可能需要看一下Haskell中printf的实现,以及对它如何工作的描述。后一页上有一个链接,指向Oleg Kiselyov关于做这种事情的论文,这也值得一读。实际上,如果您正在设计一种功能语言,那么Oleg的网站应该是必修课。

在我看来,这些方法有些破绽,但它们表明这是可行的。但是,如果您的语言具有完全依赖的键入功能,那么它会简单得多。一个可变参数函数求和其整数参数,可能看起来像这样:

type SumType = (t : union{Int,Null}) -> {SumType, if t is Int|
                                         Int,     if t is Null}
sum :: SumType
sum (v : Int) = v + sum
sum (v : Null) = 0

用于定义递归类型而不需要为其指定一个明确名称的抽象可能会使编写此类函数更加容易。

编辑:当然,我只是再次读了一个问题,并且您说了一种动态类型的语言,这时显然类型机制并没有真正的意义,因此@amon的答案可能包含您需要的所有内容。哦,好了,我会把它留在这里,以防任何人在想知道如何使用静态语言的同时碰到这个问题……


0

这是在Python3中使用可变参数函数的版本,它利用@amon的“终止符”方法,利用了Python的可选参数:

def curry_vargs(g):
    actual_args = []
    def f(a, force=False):
        nonlocal actual_args
        actual_args.append(a)
        if force:
            res = g(*actual_args)
            actual_args = []
            return res
        else:
            return f
    return f

def g(*args): return sum(args)
f = curry_vargs(g)
f(1)(2)(3)(4,True) # => 10

返回的函数f收集在外部范围内绑定的数组中的连续调用中传递给它的参数。仅当force参数为true 时,才调用原始函数,并收集到目前为止的所有参数。

此实现的注意事项是,您始终必须将第一个参数传递给它,f因此您无法创建“ thunk”(该函数绑定了所有参数,并且只能使用空参数列表来调用)(但我认为这与咖喱的典型实现)。

另一个警告是,一旦您传递了错误的参数(例如,错误的类型),就必须重新执行原始函数。没有其他方法可以重置内部阵列,只有在成功执行curried函数之后才能执行此操作。

我不知道您的简化问题“对象可以同时是一个函数和一个非函数值吗?”是否可以在Python中实现,因为对没有括号的函数的引用会评估内部函数对象。我不知道这是否可以弯曲以返回任意值。

在Lisp中这可能很容易,因为Lisp符号可以同时具有一个值和一个函数值。当符号出现在功能位置(作为列表中的第一个元素)时,只需选择功能值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.