使用Church数字键入某些操作的签名声明

aco*_*ell 2 haskell church-encoding

我试图在Haskell中实现教会数字.这是我的代码:

-- Church numerals in Haskell.
type Numeral a = (a -> a) -> (a -> a)

churchSucc :: Numeral a -> Numeral a
churchSucc n f = \x -> f (n f x)

-- Operations with Church numerals.
sum :: Numeral a -> Numeral a -> Numeral a
sum m n = m . churchSucc n

mult :: Numeral a -> Numeral a -> Numeral a
mult n m = n . m

-- Here comes the first problem
-- exp :: Numeral a -> Numeral a -> Numeral a
exp n m = m n

-- Convenience function to "numerify" a Church numeral.
add1 :: Integer -> Integer
add1 = (1 +)

numerify :: Numeral Integer -> Integer
numerify n = n add1 0

-- Here comes the second problem
toNumeral :: Integer -> Numeral Integer
toNumeral 0 = zero
toNumeral (x + 1) = churchSucc (toNumeral x)
Run Code Online (Sandbox Code Playgroud)

我的问题来自取幂.如果我声明了toNumeral和的类型签名exp,则代码不会编译.但是,如果我评论类型签名声明,一切正常.什么是正确的声明toNumeralexp

HTN*_*TNW 6

原因exp无法按照你所拥有的方式编写,因为它涉及将Numeralas参数传递给a Numeral.这需要一个Numeral (a -> a),但你只有一个Numeral a.你可以把它写成

exp :: Numeral a -> Numeral (a -> a) -> Numeral a
exp n m = m n
Run Code Online (Sandbox Code Playgroud)

toNumeral除了x + 1不应该使用的模式之外,我没有看到有什么问题.

toNumeral :: Integer -> Numeral a -- No need to restrict it to Integer
toNumeral 0 = \f v -> v
toNumeral x
  | x > 0 = churchSucc $ toNumeral $ x - 1
  | otherwise = error "negative argument"
Run Code Online (Sandbox Code Playgroud)

此外,你sum被窃听,因为它m . churchSucc nm * (n + 1),所以它应该是:

sum :: Numeral a -> Numeral a -> Numeral a
sum m n f x = m f $ n f x -- Repeat f, n times on x, and then m more times.
Run Code Online (Sandbox Code Playgroud)

然而,教堂数字是适用于所有类型的功能.也就是说,Numeral String不应该与之不同Numeral Integer,因为它Numeral不应该关心它正在做什么类型.这是一个普遍的量化:Numeral是一个函数,用于所有类型的a,(a -> a) -> (a -> a),写的是与RankNTypes作为type Numeral = forall a. (a -> a) -> (a -> a).

这是有道理的:教会数字是由其重复的函数参数的次数来定义的.\f v -> v调用f0次,所以它是0,\f v -> f v是1等.强制一个Numeral为所有人工作a确保它只能这样做.但是,允许一个Numeral关心什么类型fv删除限制,并让你写(\f v -> "nope") :: Numeral String,即使这显然不是一个Numeral.

我会写这个

{-# LANGUAGE RankNTypes #-}

type Numeral = forall a. (a -> a) -> (a -> a)

_0 :: Numeral
_0 _ x = x
-- The numerals can be defined inductively, with base case 0 and inductive step churchSucc
-- Therefore, it helps to have a _0 constant lying around

churchSucc :: Numeral -> Numeral
churchSucc n f x = f (n f x) -- Cleaner without lambdas everywhere

sum :: Numeral -> Numeral -> Numeral
sum m n f x = m f $ n f x

mult :: Numeral -> Numeral -> Numeral
mult n m = n . m

exp :: Numeral -> Numeral -> Numeral
exp n m = m n

numerify :: Numeral -> Integer
numerify n = n (1 +) 0

toNumeral :: Integer -> Numeral
toNumeral 0 = _0
toNumeral x
  | x > 0 = churchSucc $ toNumeral $ x - 1
  | otherwise = error "negative argument"
Run Code Online (Sandbox Code Playgroud)

相反,它看起来更干净,并且不太可能遇到障碍而不是原始路障.

演示:

main = do out "5:" _5
          out "2:" _2
          out "0:" _0
          out "5^0:" $ exp _5 _0
          out "5 + 2:" $ sum _5 _2
          out "5 * 2:" $ mult _5 _2
          out "5^2:" $ exp _5 _2
          out "2^5:" $ exp _2 _5
          out "(0^2)^5:" $ exp (exp _0 _2) _5
       where _2 = toNumeral 2
             _5 = toNumeral 5
             out :: String -> Numeral -> IO () -- Needed to coax the inferencer
             out str n = putStrLn $ str ++ "\t" ++ (show $ numerify n)
Run Code Online (Sandbox Code Playgroud)