在Haskell函数类型(->
)中给出,它不是代数数据类型构造函数,并且无法将其重新实现为与(->
)相同。
所以我想知道,哪些语言可以让我写出我的{({1}})版本?这个属性如何调用?
UPD 由于讨论,问题的重新制定:
哪种语言没有->
作为基本类型?
为什么->
是必要的原语?
答案 0 :(得分:12)
我想不出任何将箭头作为用户定义类型的语言。原因是箭头 - 函数的类型 - 在中烘焙到类型系统,一直到简单类型的lambda演算。箭头类型必须是语言的基础,这直接来自于你在lambda演算中形成函数的方式是通过lambda抽象(在类型级别引入箭头)。
尽管Marcin恰如其分地指出你可以用自由点编程,但这并没有改变你正在做的事情的本质。使用没有箭头类型的语言作为原语违背了Haskell最基本的构建块。 (您在问题中引用的语言。)
将箭头作为基本类型也与构造逻辑共享一些重要的联系:您可以将函数箭头类型视为直觉逻辑的含义,并将该类型的程序称为“校样”。 (也就是说,如果你有类型A - > B的东西,你有一个证据,它采用A类的一些前提,并为B生成证据。)
你对使用含有箭头的语言感到不安的事实可能意味着你并没有从根本上理解为什么他们如此依赖于语言的设计,也许是时候阅读几章了来自Ben Pierce的“类型和编程语言”link.
编辑:您总是可以查看那些没有强大功能概念的语言,并且可以根据其他方式定义语义 - 例如,即时或PostScript - 但是在这些语言中,您没有定义归纳数据类型的方式与Haskell,ML或Coq等函数式语言相同。换句话说,在为数据类型定义构造函数的任何语言中,箭头都是从这些类型的构造函数中自然产生的。但是在您没有以典型方式定义归纳数据类型的语言中,您不会自然地获得箭头类型,因为语言不会以这种方式工作。
另一个编辑:我会再坚持一次评论,因为我昨晚想到了它。函数类型(和函数抽象)构成了几乎所有编程语言的基础 - 至少在某种程度上,即使它是“幕后”。但是,有些语言用于定义其他语言的语义。虽然这与您所说的不完全匹配,但PLT Redex是一个这样的系统,用于指定和调试编程语言的语义。从实践者的角度来看,它并不是非常有用(除非你的目标是设计新的语言,在这种情况下它 非常有用),但也许这符合你想要的。
答案 1 :(得分:3)
函数定义通常是原始的,因为(a)函数是程序完成任务的方式; (b)这种lambda抽象是必要的,能够以有点的方式编程(即使用明确的参数)。
可能最接近符合条件的语言是基于纯粹无点模型的语言,它允许您创建自己的lambda运算符。您可能希望一般地探索无点语言,特别是基于SKI演算的语言:http://en.wikipedia.org/wiki/SKI_combinator_calculus
在这种情况下,你仍然有原始函数 types ,而且你总是会,因为它是类型系统的基本元素。如果你想完全摆脱这种情况,你可能做的最好的事情就是基于函数的类别理论概括的某种类型系统,这样函数就是另一种类型的特殊情况。请参阅http://en.wikipedia.org/wiki/Category_theory。
答案 2 :(得分:3)
你的意思是像SICP那样的元循环评估者吗?能够编写自己的DSL吗?如果您创建自己的“功能类型”,则必须自己负责“应用”它。
举个例子,您可以在C中创建自己的“函数”,使用包含函数指针的查找表,并使用整数作为函数。当然,您必须为这些“功能”提供自己的“呼叫”功能:
void call( unsigned int function, int data) {
lookup_table[function](data);
}
您可能还想要一些从原始函数创建更复杂函数的方法,例如使用整数数组来表示“原始函数”1, 2, 3, ...
的顺序执行,并最终为自己创建全新的语言。
我认为早期的汇编程序无法创建可调用的“宏”并且必须使用GOTO。
您可以使用trampolining来模拟函数调用。您可能只有全局变量存储,或许浅层绑定。在这种语言中,“函数”可以定义,但不是原始类型。
因此,没有必要使用语言中的函数,尽管方便。
在Common Lisp中defun
只是一个关联名称和可调用对象的宏(尽管lambda
仍然是内置的)。在AutoLisp中,最初根本没有特殊的函数类型,函数直接由引用的 s - 表达式列表表示,第一个元素是参数列表。您可以直接在AutoLisp中使用符号中的cons
和list
函数来构建函数:
(setq a (list (cons 'x NIL) '(+ 1 x)))
(a 5)
==> 6
某些语言(如Python)支持多种基本函数类型,每种类型都有其调用协议 - 即,生成器支持多次重入和返回(即使在语法上通过使用相同的def
关键字)。您可以轻松地想象一种语言,它可以让您定义自己的调用协议,从而创建新的函数类型。
编辑:作为一个例子考虑在函数调用中处理多个参数,在自动currying或自动可选args之间进行选择等。在Common LISP中,你可以很容易地创建两个不同的{{ 1}}宏直接表示两个调用协议。考虑函数不是通过聚合(在Haskell中的元组)中返回多个值,而是直接返回指定的接收vars / slots。所有都是不同类型的功能。
答案 3 :(得分:3)
哪种语言没有 - >作为原始类型?
好吧,如果你的意思是一种可以命名的类型,那么有许多语言没有它们。功能不是一流委员会的所有语言都没有 - >作为一种你可以在某处提到的类型。
但是,正如@Kristopher雄辩且出色地解释的那样,函数是(或者至少可以被认为是)所有计算的基本构建块。因此,即使在Java中,也就是有功能,但它们会被小心地隐藏起来。
并且,正如有人提到汇编程序 - 人们可以认为机器语言(大多数现代计算机)是寄存器机器模型的近似值。但它是如何完成的?拥有数百万和数十亿的逻辑电路,每个逻辑电路都是非常原始的纯函数(如NOT或NAND)的实现,以某种物理顺序排列(显然,这是硬件生成器实现函数组合的方式) )。 因此,虽然您可能看不到机器代码中的功能,但它们仍然是基础。
答案 4 :(得分:3)
在Martin-Löf type theory中,函数类型通过indexed product types (so-called Π-types)定义。
基本上,从A
到B
的函数类型可以解释为(可能是无限的)记录,其中所有字段都是相同类型B
和字段名称正是A
的所有元素。当您需要将函数f
应用于参数x
时,您需要在与f
对应的x
中查找字段。
维基百科文章lists一些基于Martin-Löf类型理论的编程语言。我不熟悉它们,但我认为它们可以回答你的问题。
答案 5 :(得分:3)
Philip Wadler的论文Call-by-value is dual to call-by-name提出了一个微积分,其中变量抽象和协变抽象比函数抽象更原始。根据这些原语提供了两种函数类型的定义:一种是按值调用,另一种是按名称调用。
受到Wadler论文的启发,我实现了一种语言(Ambidexer),它提供了两种函数类型构造函数,它们是从基元构造的类型的同义词。一个用于按值调用,一个用于按名称调用。 Wadler的双重微积分和Ambidexter都不提供用户定义的类型构造函数。但是,这些示例表明函数类型不一定是原始的,并且可以设想您可以定义自己的语言( - >)。
答案 6 :(得分:2)
在Scala中,您可以混合其中一个Function
特征,例如Set[A]
可以用作A => Boolean
,因为它实现了Function1[A,Boolean]
特征。另一个例子是PartialFunction[A,B]
,它通过提供“范围检查”方法isDefinedAt
来扩展常规功能。
但是,在Scala中,方法和函数是不同的,并且没有办法改变方法的工作方式。通常您不会注意到差异,因为方法会自动提升到函数。
因此,您可以很好地控制在Scala中实现和扩展函数的方式,但我认为您有一个真正的“替代”。我不确定这是否有意义。
或许您正在寻找具有某种功能泛化的语言?然后使用Arrow语法的Haskell符合条件:http://www.haskell.org/arrows/syntax.html
答案 7 :(得分:2)
我认为你问题的愚蠢答案是汇编代码。这为您提供了比函数更“低”的基元。您可以将函数创建为使用寄存器和跳转基元的宏。
大多数理智的编程语言都会为您提供一种创建函数作为烘焙语言功能的方法,因为函数(或“子例程”)是良好编程的本质:代码重用。