Haskell 如何处理更高级别的类型
玩教堂的数字。我遇到的情况是,我无法指导GHC类型检查器处理高阶类型 首先,我编写了一个没有任何类型签名的版本:Haskell 如何处理更高级别的类型,haskell,Haskell,玩教堂的数字。我遇到的情况是,我无法指导GHC类型检查器处理高阶类型 首先,我编写了一个没有任何类型签名的版本: module ChurchStripped where zero z _ = z inc n z s = s (n z s) natInteger n = n 0 (1+) add a b = a b inc {- *ChurchStripped> natInteger $ add (inc $ inc zero) (inc $ inc $ inc zero) 5 -}
module ChurchStripped where
zero z _ = z
inc n z s = s (n z s)
natInteger n = n 0 (1+)
add a b = a b inc
{-
*ChurchStripped> natInteger $ add (inc $ inc zero) (inc $ inc $ inc zero)
5
-}
mult a b = a zero (add b)
{-
*ChurchStripped> natInteger $ mult (inc $ inc zero) (inc $ inc $ inc zero)
6
-}
mult
的推断类型非常糟糕,因此我尝试使用类型定义清理这些类型:
module Church where
type Nat a = a -> (a -> a) -> a
zero :: Nat a
zero z _ = z
inc :: Nat a -> Nat a
inc n z s = s (n z s)
natInteger :: Nat Integer -> Integer
natInteger n = n 0 (1+)
{- `add :: Nat a -> Nat a -> Nat a` doesn't work, and working signature looks already suspicious -}
add :: Nat (Nat a) -> Nat a -> Nat a
add a b = a b inc
{-
*Church> natInteger $ add (inc $ inc zero) (inc $ inc $ inc zero)
5
-}
mult :: Nat (Nat a) -> Nat (Nat a) -> Nat a
mult a b = a zero (add b)
{-
*Church> natInteger $ mult (inc $ inc zero) (inc $ inc $ inc zero)
6
-}
这是可行的,但类型并不像它们可能的那样干净。以下是我尝试过的定义:
{-# LANGUAGE RankNTypes #-}
module SystemF where
type Nat = forall a. a -> (a -> a) -> a
zero :: Nat
zero z _ = z
inc :: Nat -> Nat
inc n z s = s (n z s)
natInteger :: Nat -> Integer
natInteger n = n 0 (1+)
{- This doesn't work anymore
add :: Nat -> Nat -> Nat
add a b = a b inc
Couldn't match type `forall a1. a1 -> (a1 -> a1) -> a1'
with `a -> (a -> a) -> a'
Expected type: (a -> (a -> a) -> a) -> a -> (a -> a) -> a
Actual type: Nat -> a -> (a -> a) -> a
In the second argument of `a', namely `inc'
In the expression: a b inc
In an equation for `add': add a b = a b inc
-}
我想应该可以使用Nat->Nat->Nat->Nat
类型签名编写add
,但我不知道如何编写
另外,实际上我是从底部开始的,但用这种方式来表示这个问题可能更容易。我不太理解
RankNTypes
来解释为什么原始示例不起作用,但是如果将Nat打包到数据类型中,那么它就起作用了:
{-# LANGUAGE RankNTypes #-}
module SystemF where
data Nat = Nat (forall a. (a -> (a -> a) -> a))
zero :: Nat
zero = Nat const
inc :: Nat -> Nat
inc (Nat n) = Nat $ \z s -> s $ n z s
natInteger :: Nat -> Integer
natInteger (Nat n) = n 0 (1+)
add :: Nat -> Nat -> Nat
add (Nat a) b = a b inc
现在,
Nat
类型是一种真正的数据类型,这有助于类型检查器,因为它不必一直处理多态类型,只有在您实际“解包”它时才需要 bennofs是对的,您确实想在这里帮助typechecker,特别是在添加中,您需要在中为所有a实例化a
。a->(a->a)->a
与Nat
(即,所有a...
类型的相同)
一种方法是引入一个包装多态类型的新类型:
newtype Nat' = N Nat
现在,您可以通过N
在Nat
和Nat'
之间切换,然后使用unN
unN :: Nat' -> Nat
unN (N n) = n
(在这一点上值得注意的是,newtypenat'=nnat
与datanat2=forall a.N2(a->(a->a)->a)是不同的beast)
。后者需要-XExistentialQuantification
,因为它表示对于a
的某些特定选择,您可以进行Nat2
。另一方面,前者仍然表示如果您有a->(a->a)->a
对于任意的a
,您可以创建一个Nat'
。对于Nat'
,您需要-XRankNTypes
,但不需要存在主义。)
现在我们还可以使inc'
增加Nat'
:
inc' :: Nat' -> Nat'
inc' (N n) = N (inc n)
我们准备补充:
add :: Nat -> Nat -> Nat
add n m = unN (n (N m) inc')
这样做之所以有效,是因为现在不再试图说服GHC使用多态类型∀ A.a->(a->a)->a本身,N
充当提示
例如:
> natInteger (add (inc zero) (inc zero))
2
以下是我对教会数字的实现:
type Nat = forall a . (a→a) → (a→a)
zero :: Nat
zero _ = id
one :: Nat
one = id
inc :: Nat → Nat
inc a f = f . a f
add :: Nat → Nat → Nat
add a b f = (a f) . (b f)
mul :: Nat → Nat → Nat
mul a b = a . b
nat :: Integer → Nat
nat 0 = zero
nat n = inc $ nat (n-1)
unnat :: Nat → Integer
unnat f = f (+ 1) 0
翻转它们更容易操作(函数首先应用N次,其次是它的参数)。一切都会自然而然地出现
编辑:此解决方案也受到限制,类型与原始问题不同,在某个时候会出现故障。似乎通过使用Data.Proxy
我们可以给GHC一个提示:
{-# LANGUAGE RankNTypes #-}
import Data.Proxy
type Nat = forall a. Proxy a -> (a -> a) -> (a -> a)
zero :: Nat
zero _ _ x = x
suc :: Nat -> Nat
suc n proxy s z = n proxy s (s z)
-- add = \m n f x. m f (n f x)
add :: Nat -> Nat -> Nat
add n m proxy s z = n proxy s (m proxy s z)
-- mult = \m n f. m (n f)
mult :: Nat -> Nat -> Nat
mult m n proxy s z = m proxy (n proxy s) z
然后它就成功了
λ > :t let one = suc zero in add one one
let one = suc zero in add one one :: Proxy a -> (a -> a) -> a -> a
λ > let one = suc zero in add one one Proxy (1+) 0
2
λ > :t let two = suc (suc zero) in mult two two
let two = suc (suc zero) in mult two two
:: Proxy a -> (a -> a) -> a -> a
λ > let two = suc (suc zero) in mult two two Proxy (1+) 0
4
forall引入了更高级别的类型,这肯定不是您想要的。@Ingo我不明白为什么不换个说法:您在类型中提到的每个Nat
都有自己的a
类型变量,不能与您签名中的任何其他类型变量统一,但显然,我们在写Nat->Nat
时希望a
s保持不变。@Ingo,不,真的希望它保持通用性。这有点像idid
case(这不是很有趣,但很有效)。也许您想将类型定义为(a->a)->(a->a)
。很好。我仍然想知道为什么最初的示例不起作用,是因为forall
被移动了,如果不是绑定在数据类型或其他内容中的话。似乎这是目前唯一合理的解决方案,正如ExplicitTypeApplication最快将在ghc 7.12.1中出现一样,用自身量化的类型实例化量化类型变量的能力被称为“不确定性”。系统F(和F-omega)都具有不可预测性,Haskell/GHC(默认情况下)没有。GHC实际上对非指示性类型(使用-ximpeditivetypes
)有一些支持,但实现不是很可预测的,它侧重于使用多态类型实例化数据类型参数(例如可能(对于所有的a…)
。@kosmikus,你说得对。这与存在主义无关。更新了我的答案以反映这一点。所以没有任何方法在Haskell中实例化通用限定术语,即显式地给出类型参数?它总是由类型推断检查引擎推断出来吗?@OlegGrenrus它没有特定的语法,只有在GHC的核心语言中。但是,这里有人提出:注意,对于单态类型,只需通过类型注释函数即可强制实例化,例如,id::Int->Int
将id
实例化为typeInt
。但是根据您对多态类型Nat
的定义,id::Nat->Nat
将失败。只需将add
扩展即可(即,将add::Nat->Nat->a->a
实现为一个包含4个参数的函数)。整个Nat'
的事情似乎是一条不必要的绕道。我想真正的问题是,这是一个指导GHC在特定地方应用HM gen和inst规则的练习。这对于这种特殊情况来说是一个很好的解决方案,但它仍然不是通用的解决方案。例如,如果我尝试以类似的方式制作一个列表:同样的问题会出现(或者我也可以在那里做一些技巧,但不清楚是哪一个)。我会接受@kosmikus的评论,如果这是一个实际的答案。嗯。显然我误解了这个问题。我的Nat示例实际上也是有限的,类型不正确,它迟早会崩溃。