【问题标题】:How to work with higher rank types如何使用更高级别的类型
【发布时间】:2014-01-21 13:45:20
【问题描述】:

玩弄教堂的数字。我遇到了无法引导 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
-}

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
-}

它可以工作,但类型不够干净。按照我尝试的System F 定义:

{-# 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类型签名写add,但我不知道怎么做。

附:其实我是从底层开始的,不过这样表述这个问题可能更容易些。

【问题讨论】:

  • forall 引入了排名更高的类型,这肯定不是你想要的。
  • @Ingo 我不明白为什么不
  • 换一种说法:您在类型中提到的每个Nat 都会有自己的a 类型变量,它不能与您签名中的任何其他类型变量统一,但显然,我们写Nat -> Nat 你希望as 是一样的。
  • @Ingo,不,真的希望它保持通用性。这有点像id id 案例(这不是很有趣,但很有效)。 gist.github.com/phadej/8540770
  • 也许你想将类型定义为(a->a)->(a->a)

标签: haskell


【解决方案1】:

bennofs 是对的,你真的想在这里帮助类型检查器,特别是在 add 中,你需要用 Nat 实例化 forall a . a -> (a -> a) -> a 中的 a (即,相同的 forall a . ... 类型) .

一种方法是引入一个包装多态类型的新类型:

newtype Nat' = N Nat

现在您可以通过NNatNat' 之间切换,然后使用unN 返回

unN :: Nat' -> Nat
unN (N n) = n

(此时值得注意的是newtype Nat' = N Natdata Nat2 = forall a . N2 (a -> (a -> a) -> a) 是不同的野兽。后者需要-XExistentialQuantification,因为它表示对于a 的某些特定选择,您可以创建Nat2。另一方面,前者仍然说,如果你有任意aa -> (a -> a) -> a,那么你可以创建一个Nat'。对于Nat',你需要-XRankNTypes,但你不需要existentials。 )

现在我们也可以让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 的类型,而是 N 充当提示。

例子:

> natInteger (add (inc zero) (inc zero))
2

【讨论】:

  • 用本身量化的类型实例化量化类型变量的能力称为“不可预测性”。 System F(和 F-omega)都具有不可预测性,Haskell/GHC(默认情况下)没有。 GHC 实际上对非谓词类型有一些支持(-XImpredicativeTypes),但实现不是很可预测,并且侧重于用多态类型(例如Maybe (forall a. ...))实例化数据类型参数。
  • @kosmikus,你是对的。这与存在主义无关。更新了我的答案以反映这一点。
  • 所以没有任何方法可以在 Haskell 中实例化通用限定术语,即显式给出类型参数?它总是由类型推断检查引擎推断出来的吗?
  • @OlegGrenrus 它没有特定的语法,只有 GHC 的核心语言。但是,它已被提议,例如在这里:ghc.haskell.org/trac/ghc/wiki/TypeApplication 请注意,对于单态类型,您可以简单地通过对函数进行类型注释来强制实例化,例如id :: Int -> Intid 实例化为类型 Int。但是根据您对多态类型 Nat 的定义,id :: Nat -> Nat 将失败。
  • 嗯。只是 eta-expanding add 也可以工作(即,将 add :: Nat -> Nat -> a -> (a -> a) -> a 实现为一个接受 4 个参数的函数)。这整个Nat' 的事情似乎是不必要的绕道。我想真正的重点是,这都是指导 GHC 在特定地方应用 HM gen 和 inst 规则的练习。
【解决方案2】:

我不太了解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 类型是一个真正的数据类型,这有助于类型检查器,因为它不必一直处理多态类型,只有当你真正“解包”它时。

【讨论】:

  • 不错。我仍然想知道为什么原始示例不起作用,是不是因为 forall 被移动了,如果没有绑定在数据类型或其他东西中。
  • 似乎这是目前唯一合理的解决方案,因为 ExplicitTypeApplication 将最快登陆 ghc 7.12.1 ghc.haskell.org/trac/ghc/ticket/4466
【解决方案3】:

这是我对教堂数字的实现:

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 次,然后是它的参数)。一切都出来了,嗯,自然

编辑:这个解决方案也是有限的,类型不正确,就像原来的问题一样,它会在某个时候崩溃。

【讨论】:

  • 对于这种特殊情况来说这是一个很好的解决方案,但它仍然不是通用的解决方案。例如,如果我尝试以类似的方式制作列表:gist.github.com/phadej/8594017 会出现同样的问题(或者我也可以在那里做一些技巧,但不清楚是哪一个)。如果这是一个实际的答案,我会接受@kosmikus 关于ghc.haskell.org/trac/ghc/wiki/TypeApplication 的评论。
  • 嗯。显然我误解了这个问题。我的Nat例子其实也是有限的,类型不对,迟早会坏的。
【解决方案4】:

看来使用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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-10-12
    • 2011-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-14
    • 2011-02-03
    • 1970-01-01
    相关资源
    最近更新 更多