递归方法如何工作?

时间:2018-03-22 16:20:34

标签: haskell recursion runtime typeclass dependent-type

考虑以下方法count将类型级自然数映射到值级自然数:

{-# LANGUAGE
    DataKinds
  , KindSignatures
  , PolyKinds
  , FlexibleInstances
  , FlexibleContexts
  , ScopedTypeVariables
  #-}


module Nat where

data Nat = Z | S Nat

data Proxy (a :: k) = Proxy

class Count a where
    count :: a -> Int

instance Count (Proxy Z) where
    count _ = 0

instance Count (Proxy n) => Count (Proxy (S n)) where
    count _ = succ $ count (Proxy :: Proxy n)

似乎在repl中工作:

λ count (Proxy :: Proxy (S(S(S Z))) )
3

对于终止的递归,在Proxy的类型的运行时必须有一些指示,但是类型应该在运行时被擦除。我甚至可以用data定义中的newtype替换Proxy

newtype Proxy (a :: k) = Proxy ()

- 每次都要求它拥有相同的内存表示,因此它是Coercible。考虑到这一点:

  1. 我完全不明白如何调度方法。我会理论化,或者:

    • 表格(类型,方法名称)⟶功能由编译器生成。然后,在运行时,所有对象都使用其类型进行标记,而方法是一个高阶函数,它查看类型标记并在表中查找相应的函数。但人们说在编译过程中类型被完全删除,所以这并没有加起来。
    • 表格方法名称⟶功能附加到每个对象,方法调用表示为方法名称。然后,函数应用程序查找相应的 Function 并在强制它时应用它。为了节省空间,该表可以由该类型的所有成员共享,但是与使用类型标记的对象没有区别。
    • 表格(方法名称,实例索引)⟶函数由表格生成,表格(方法名称 - >实例索引)表格为在运行时附加到对象。这意味着对象不知道它的类型,但知道它所属的类,以及正确的实例选择。我不知道这种方法是否有任何好处。

    因此,如果对象没有以某种方式直接或间接标记其类型,我不明白运行时系统如何确定方法实例的正确选择。周围的人都在谈论一些字典传递的东西,但我完全不明白:

    • 有什么关键?
    • 有什么价值?
    • 字典在哪里? (在堆上,在程序文本中,还在其他地方?)
    • 谁有指向字典的指针?

    ......等等。

  2. 即使有一个技巧允许选择方法实例而不用类型标记对象,但只有2个Count实例,所以方法的选择只能带1一点信息。 (例如,可能有Proxy标记为“将方法A 1 应用于我”,以及A <中的方法实例sub> 1 使用“将实例A 0 中的方法应用于我”来重置Proxy。这显然是不够的。运行时必须有一些东西在每次应用递归实例时都会下降。

  3. 您能指导我执行此代码,还是引入一些描述运行时系统适用细节的链接?

2 个答案:

答案 0 :(得分:7)

每当约束出现在函数声明的LHS时,如

count :: (Count a) => a -> Int

它是

的语法糖
count' :: CountDictionary a -> a -> Int

其中CountDictionary a是适合运行时的(但是单例 - 编译器总是为每种类型选择一个实例!)实际上是{{1}的方法的表示} class,即

Count

在我进一步阐述之前,让我在没有那些丑陋代理的情况下重写所有内容,而不是data CountDictionary a = CountDict { countMethod :: a -> Int }

TypeApplications

现在当你写{-# LANGUAGE AllowAmbiguousTypes, TypeApplications, ScopedTypeVariables, UnicodeSyntax #-} class Count a where count :: Int ⇒ count' :: CountDictionary a -> Int w/ data CountDictionary a = CountDict Int instance Count Z where count = 0 instance ∀ n . Count n => Count (S n) where count = succ $ count @n 时,它由

代表
count @(S(S(S Z)))

答案 1 :(得分:5)

类型类被去掉了记录。一切都在编译时发生。

data Count a = Count { count :: a -> Int }

instance_Count_ProxyZ :: Count (Proxy Z)
instance_Count_ProxyZ = Count { count = \_ -> 0 }

instance_Count_ProxySn :: Count (Proxy n) -> Count (Proxy (S n))
instance_Count_ProxySn context = Count {
  count = \_ -> succ (count context (Proxy :: Proxy n)) }

每当我们调用count :: Count n => n -> Int时,desberarer(在typechecker之后运行)会查看n的推断类型,并尝试构造类型为Count n的记录。

因此,如果我们写count (Proxy :: Proxy (S (S (S Z)))),我们需要Count (S (S (S Z)))类型的记录,唯一匹配的实例是Count (Proxy n) -> Count (Proxy (S n))n ~ S (S Z)。这意味着我们现在必须构造其类型为Count (Proxy (S (S Z)))的参数,依此类推。

请注意,这也是count实例中Proxy (S n)的申请过程中发生的情况。

在此过程之后,没有剩下的类型类,一切都只是记录。