正如我之前所说,PyFL是我们其他人的函数式编程。(可在pyflang.com上找到。)
PyFL现在有类型检查功能——没有类型声明。相反,类型是通过在类型域上评估程序而生成的。
在PyFL中,所有普通人觉得困难或完全奇怪的事情都消失了:单子、强制咖喱、后置或前缀符号、模式匹配等,而不是函数应用的中缀符号和f(x,y,z)语法。这种奇怪的东西有它的支持者,但PyFL证明它并不是函数式编程的固有组成部分。
尤其是繁琐、冗长的类型声明。在Haskell的大多数编程示例中,都是从这些声明开始的。在PyFL中,你跳过了这个阶段。
这并不意味着你可以在没有听说的情况下对字符串进行乘法。PyFL是动态类型的,并在运行时检查计算是否合理。
然而,运行时计算意味着您必须实际运行程序才能找到这些类型的错误——并且幸运地遇到它们。他们不一定每次都会暴露自己。
正如一位退休的专业人士所指出的,你可能会发布该软件,然后会有成千上万的用户与动态类型检查器发生冲突。
因此,仍然需要进行静态分析以避免运行时错误。需要编译时类型检查。
但是如何在没有类型声明的语言中进行类型检查呢?我真的不想再加上它们——那将是对“为我们其余人”原则的一次大倒退。
幸运的是,有一个解决方案——类型推断。这意味着在不打扰程序员的情况下,分析程序并推断出至少一些类型。
a+f(b),其中a=4;b=a+9;f(n)=如果n<;2然后1其他3*f(n-1)+1 fi;终止
很明显,a是一个整数,因此b也是。这些事实源于两个整数之和是整数的基本类型规则。不需要声明b:int。
f(b)似乎也是一个整数,尽管在这个阶段还不清楚你将如何正式证明它。这就是我们要解决的问题。
推断类型与计算类似,只是放弃了实际数据,而是合并了类型。使用这样的规则
int+int=intnum+int=numint*int=intint/int=numnum<;num=boolif bool然后int else int fi=int
当然你会失去信息;特别是,您不知道将选择条件的哪一个分支。这意味着计算递归程序是有问题的,因为它们使用条件来触发终止。
请注意,此类型方案同时包含int(整数)和num(数字)。注意int也是数字,例如。。g、 num+int=num。这里int是num的子类型。还有两个其他子类型:intlist是numlist的子类型,而numlist又是list的子类型。intlist的头是int,numlist的尾是numlist,依此类推。
实现这种偏序需要大量的编码,我没有使用单独的stringlist和wordlist类型,更不用说listlist了。我写了一个函数sb(p,q)来测试p是否是q的一个子类型,以及一个函数lub(p,q)来给出p和q的最小上界。
显然,我的领域远远没有达到模仿Haskell类型声明的细微差别所必需的水平。例如,在Haskell中,可以将变量声明为整数列表。我需要一个无限偏序。但它适用于范围广泛的程序,并捕捉到范围广泛的错误。
因此,我提出的计划是使用PyFL计算器并对其进行修改,这样它就不会产生程序输出的实际数据,而是输出通过在抽象类型域上“运行”程序而产生的类型。这是抽象解释的基本思想,对我来说很难有独创性。
该方案的优点之一是,它不仅避免了程序员类型声明,还避免了函数类型。这是上面给出的程序的高阶变体
a+f(b),其中db(g)=λ(x)g(g(x))结束;inc(u)=u+2;f=db(公司);a=4;b=5;终止
它的计算结果为int,尽管涉及二阶函数db。
递归的问题在于,计算不会终止。这让我有一段时间感到困惑。
当使用通常的计算器运行时,它会迅速给出正确的答案5040。当if-then-else fi最终选择第一个备选方案时,递归终止。但是当使用类型计算器运行时,if条件仅仅是bool,必须探索这两种选择。这导致无限递归,python运行时堆栈溢出,出现分段错误,python崩溃。不用说,没有生成类型信息
与此同时,我向evaluator添加了度量,以查看生成了多少计算。其中一个指标是调用评估函数的次数。这些添加内容被转移到仅适用于替代类型的评估器中。
最后,我意识到,我可以通过在这个指标上设置一个上限来强制终止——比如30。我可以看到结果是什么,然后增加上限,看看新的结果,再次增加上限,直到一切都安定下来。
这比我预想的还要好。我在pylucid中计算维度的类似方案上进行了尝试,结果发现即使是cap的小值也给出了正确的答案。
这里的难题是,这个方案似乎显而易见,但我从未在印刷品中见过。或者任何关于如何在替代域上求值时避免非终止的讨论。然而,在另一个领域进行评估是抽象解释的基本思想。如果有人有任何见解,请与我分享。
所以我把evaluate函数的调用次数设为30次。结果证明30是一个很大的数字,我本来可以用一个小得多的帽子逃脱,但那又怎样?
现在,当我运行类型计算器时,它会快速停止并生成…int,正确。它成功地推断出整数的阶乘是整数。没有程序员的输入。
问题是,当一个评估被限制时,它会返回什么?我猜它应该返回类型域的底部元素。由于还没有一个底层元素,我添加了一个:类型none。类型none不是任何数据对象的类型。如果将一个类型视为一个集合(该类型的所有对象的集合),则“无”是空集。
我必须制定出无条件计算的规则。由于none是num的子类型,它可以作为num,所以我们有规则none+num=num(以及none+none=none)
同样,如果我们计算f(x,y)并且f计算为无,结果应该是无。我承认这是猜测,但它给出了正确的答案。
这需要重写sb和lub,这变得相当复杂。如果我想扩展我的领域,我必须想出更系统的方法。
阶乘程序之所以有效,是因为PyFL(和Haskell)内置了递归,计算器在定义fac的相同环境中评估fac的定义。但如果递归不是内置的呢?我们还能定义阶乘吗?这就是lambda演算(没有内置递归)开发人员面临的问题。
起初,这似乎不太可能。但后来库里发明了Y组合器。给你
它只是一个小的表达式,具有神奇的性质,Yf减少到f(Yf)。它在PyFL符号中有一个很好的定义,即
在这种形式下,很容易看出它是有效的。我们用g代替方程中的x,得到g(g)=f(g(g))。
式中Y(f)=g(g),其中g(x)=f(x(x))结束;a(f)=λ(n)如果n<;2然后1否则n*f(n-1)fi结束;fac=Y(a);终止
请注意,这个程序是非递归的:没有直接或间接地定义变量本身。
这个程序不能用Haskell编写。它使用无法键入的自我应用程序。显然,我们可以用Haskell编写阶乘,但不是这样。我们必须求助于Haskell的内置递归,比如定义Y(f)=f(Y(f))。
然后问题出现了,PyFL的类型检查呢?当我们评估这个项目的类型时会发生什么?
说实话,我不确定这是否管用。但是现在回想起来,如果PyFL可以处理Y在整数上的定义,为什么不能在类型上呢?