Haskell 递归方法是如何工作的?

Haskell 递归方法是如何工作的?,haskell,recursion,runtime,typeclass,dependent-type,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 whe

考虑以下将类型级自然数映射到值级自然数的方法
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
要终止递归,必须在运行时指示
代理的类型,但类型应该在运行时被删除。我甚至可以在
代理
定义中将
数据
替换为
newtype

newtype Proxy (a :: k) = Proxy ()
-这将迫使它每次都有相同的内存表示,因此它是可强制的。考虑到这一点:

  • 我完全不明白一个方法是如何被调度的。我认为,无论是:

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

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

    • 钥匙是什么
    • 价值观是什么
    • 字典在哪里?(在堆上,在程序文本中,还是在其他地方?)
    • 谁有字典的指针
    …等等

  • 即使存在允许选择方法实例而不使用类型标记对象的技巧,也只有2个
    Count
    实例,因此方法的选择可能只携带1位信息。(例如,可能有一个带有标签的
    代理
    ,标签上写着“从实例A1向我应用方法”,而A1中的方法实例用“从实例A0向我应用方法”重新标记
    代理
    。)这显然是不够的。每次应用递归实例时,运行时都必须有一些东西在滴答作响


  • 你能带我看一下这段代码的执行情况吗,或者加入一些描述运行时系统适用细节的链接吗?

    类型类被删除到记录中。一切都发生在编译时

    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
    ,desugarer(在类型检查器之后运行)都会查看
    n
    的推断类型,并尝试构造类型为
    count n
    的记录

    因此,如果我们编写
    count(Proxy::Proxy(S(sz))
    ,我们需要一个类型为
    count(S(sz))
    的记录,唯一匹配的实例是
    count(Proxy n)->count(Proxy(sn))
    ,带有
    n~S(sz)
    。这意味着我们现在必须构造它的参数,类型为
    Count(Proxy(S(sz)))
    ,等等

    请注意,这也是在
    代理(sn)
    实例中对
    count
    应用程序进行去糖化的过程中发生的情况


    在这个过程之后,就没有类型类了,一切都只是记录。

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

    count :: (Count a) => a -> Int
    
    就像它是糖一样

    count' :: CountDictionary a -> a -> Int
    
    其中
    CountDictionary a
    是一个运行时合适的(但singleton–编译器总是为每种类型只选择一个实例!)表示
    Count
    类的方法,即

    data CountDictionary a = CountDict {
             countMethod :: a -> Int
           }
    
    在我进一步阐述之前,请允许我重写所有内容,不使用那些丑陋的代理,而是支持
    类型应用程序

    {-# 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(sz))
    时,它由

    count @(S(S(S Z)))
      = count' ( CountDict (succ $ count @(S Z)) )
      = count' ( CountDict (succ $ count' (CountDict (succ $ count @Z))) )
      = count' ( CountDict (succ $ count' (CountDict (succ $ count' (CountDict 0)))) )
    

    吹毛求疵:Haskell没有方法,这些都是函数(方法和函数之间有一些区别)。@WillemVanOnsem那么如何调用两个同名函数?@WillemVanOnsem来自:“类声明引入一个新类及其上的操作(类方法)”。此方法不是递归的@但它显然是递归的。你能解释一下你的意思吗?@IgnatInsarov发现一件事既丑陋又流行!但不那么开玩笑:历史上每个人都使用它们,因为如果类型应用程序不存在,它是已知的类型应用程序的最干净的替代品,类型应用程序是一个非常新的功能。@IgnatInsarov它们并没有那么难看,但奇怪的是,为了选择编译时类型参数
    a
    ,还需要传递一个运行时值参数
    Proxy::Proxy MyChoiceForA
    。几年前,我们别无选择,所以代理是必要的。现在我们得到了类型application
    @MyChoiceForA
    ,这纯粹是编译时,我们不再需要代理,使代码更简单、更短。(就个人而言,我容忍
    Proxy a
    参数,不喜欢成语
    Proxy a
    ,其中
    Proxy
    是一种更高级的类型变量,可能代表
    Proxy
    ,但也代表
    [],可能,…
    )@chi这是否意味着
    count
    过去是一个多态函数,但已经成为一种持续的应用形式?@IgnatInsaro