F#中的教会数字

时间:2018-01-17 00:09:26

标签: f# functional-programming type-inference lambda-calculus church-encoding

我一直在尝试用F#实现教堂数字。他们在大学的一个课程中被简要介绍过,从那以后我可能已经离开了兔子洞。我有Predecessor,Successor,Add和Operations工作,但我无法减法工作。我试图多次实施减去b前任。我觉得奇怪的是我的代码中的倒数第二行有效,但我认为是等效的,最后一行是行不通的。存在类型不匹配。

我对F#很新,所以任何帮助都会受到赞赏。谢谢。

//Operations on tuples
let fst (a,b) = a
let snd (a,b) = b
let pair a b = (a,b)

//Some church numerals
let c0 (f:('a -> 'a)) = id
let c1 (f:('a -> 'a)) = f 
let c2 f = f << f
let c3 f = f << f << f
let c4 f = f << f << f << f

// Successor and predecessor
let cSucc (b,(cn:('a->'a)->('a->'a))) = if b then (b, fun f -> f << (cn f)) else (true, fun f -> (cn f))
let cPred (cn:('a->'a)->('a->'a)) = fun f -> snd (cn cSucc (false, c0)) f
//let cSucc2 cn = fun f -> f << (cn f)

// Add, Multiply and Subtract church numerals
let cAdd cn cm = fun f -> cn f << cm f
let cMult cn cm = cn >> cm
let cSub cn cm = cm cPred cn

//Basic function for checking validity of numeral operations
let f = (fun x -> x + 1)

//This works
(cPred << cPred) c3 f 0

//This doesn't
c2 cPred c3 f 0

这是给出的类型不匹配错误(Intellisense说这是代码最后一行的cPred错误)。我可以看到输出类型被推断错误。有没有办法解决它,或者我是如何编写这个实现的?

'((bool * (('a -> 'a) -> 'a -> 'a) -> bool * (('a -> 'a) -> 'a -> 'a)) -> bool * (('a -> 'a) -> 'a -> 'a) -> bool * (('a -> 'a) -> 'a -> 'a)) -> (bool * (('a -> 'a) -> 'a -> 'a) -> bool * (('a -> 'a) -> 'a -> 'a)) -> bool * (('a -> 'a) -> 'a -> 'a) -> bool * (('a -> 'a) -> 'a -> 'a)'    
but given a
    '((bool * (('a -> 'a) -> 'a -> 'a) -> bool * (('a -> 'a) -> 'a -> 'a)) -> bool * (('a -> 'a) -> 'a -> 'a) -> bool * (('a -> 'a) -> 'a -> 'a)) -> ('a -> 'a) -> 'a -> 'a'    
The types ''a' and 'bool * (('a -> 'a) -> 'a -> 'a)' cannot be unified.

1 个答案:

答案 0 :(得分:5)

在下面的解释中,我将假设type CN<'a> = ('a -> 'a) -> 'a -> 'a的定义(其中“CN”代表“教会数字”),以便缩短解释并减少混乱。

您尝试将c2应用于cPred失败,因为c2期望参数类型为'a -> 'a,但cPred不是此类功能。

您可能希望cPred与预期类型匹配,因为您已将其声明为CN<'a> -> CN<'a>,但这不是真正的类型。因为您要将参数cn应用于bool*CN<'a> -> bool*CN<'a>类型(cSucc的类型),编译器会推断cn的类型必须为CN<bool*CN<'a>>,并且因此,cPred的类型为CN<bool*CN<'a>> -> CN<'a>,与c2期望的内容不匹配。

所有这一切都归结为这个事实:函数在你传递它们作为值时会失去它们的通用性。

考虑一个更简单的例子:

let f (g: 'a -> 'a list) = g 1, g "a"

此类定义无法编译,因为'af的参数,而不是g的参数。因此,对于f的给定执行,必须选择特定的'a,并且它不能同时为intstring,因此,{{ 1}}无法同时应用于g1

同样,"a"中的cn已修复为cPred类型,因此bool*CN<'a> -> bool*CN<'a>的类型本身与cPred不兼容。

在简单的情况下,有一个明显的解决方法:两次传递CN<_>

g

这样,let f g1 g2 = g1 1, g2 "a" let g x = [x] f g g // > it : int list * string list = [1], ["a"] 两次都会失去通用性,但它会专门针对不同的类型 - 第一个实例为g,第二个实例为int -> int list

然而,这只是一个半尺寸,仅适用于最简单的情况。一般解决方案将要求编译器理解我们希望string -> string list成为'a的参数,而不是g的参数(通常称为"higher-rank type") 。在Haskell(更具体地说,GHC)中,有一个straightforward way to do this,启用了f扩展名:

RankNTypes

在这里,我通过在其类型声明中包含f (g :: forall a. a -> [a]) = (g 1, g "a") g x = [x] f g ==> ([1], ["a"]) ,明确告诉编译器参数g有自己的通用参数a

F#没有这样明确的支持,但它确实提供了一个可用于实现相同结果的不同功能 - 接口。接口可能具有泛型方法,并且这些方法在传递接口实例时不会失去通用性。所以我们可以像这样重新制定上面这个简单的例子:

forall a

是的,声明这种“更高级别的功能”的语法很重,但这就是F#必须提供的。

因此,将此应用于原始问题,我们需要将type G = abstract apply : 'a -> 'a list let f (g: G) = g.apply 1, g.apply "a" let g = { new G with override this.apply x = [x] } f g // it : int list * string list = ([1], ["a"]) 声明为接口:

CN

然后我们可以构建一些数字:

type CN = 
    abstract ap : ('a -> 'a) -> 'a -> 'a

然后let c0 = { new CN with override __.ap f x = x } let c1 = { new CN with override __.ap f x = f x } let c2 = { new CN with override __.ap f x = f (f x) } let c3 = { new CN with override __.ap f x = f (f (f x)) } let c4 = { new CN with override __.ap f x = f (f (f (f x))) } cSucc

cPred

请注意,let cSucc (b,(cn:CN)) = if b then (b, { new CN with override __.ap f x = f (cn.ap f x) }) else (true, cn) let cPred (cn:CN) = snd (cn.ap cSucc (false, c0)) 现在推断出cPred的类型,正是我们所需要的 算术函数:

CN -> CN

请注意,所有这些都按预期获得推断类型let cAdd (cn: CN) (cm: CN) = { new CN with override __.ap f x = cn.ap f (cm.ap f x) } let cMult (cn: CN) (cm: CN) = { new CN with override __.ap f x = cn.ap cm.ap f x } let cSub (cn: CN) (cm: CN) = cm.ap cPred cn

最后,你的例子:

CN -> CN -> CN