类型检查的实例含义

时间:2013-08-18 08:18:36

标签: agda

我正在学习如何在Agda中实现“类型类”。作为一个例子,我试图实现罗马数字,其中#的组成将进行类型检查。

  1. 我不清楚为什么Agda抱怨没有加入的实例(罗马_ _)(罗马_ _)_ - 很明显,它无法找出替代那里的自然数字。

  2. 有没有更好的方法来引入没有“构造函数”形式的罗马数字?我有一个构造函数“madeup”,它可能需要是私有的,以确保我只有“可信”的方式通过Join构造其他罗马数字。

    module Romans where
    
      data ℕ : Set where
        zero : ℕ
        succ : ℕ → ℕ
    
      infixr 4 _+_ _*_ _#_
    
      _+_ : ℕ → ℕ → ℕ
      zero + x = x
      succ y + x = succ (y + x)
    
      _*_ : ℕ → ℕ → ℕ
      zero * x = zero
      succ y * x = x + (y * x)
    
      one = succ zero
    
      data Roman : ℕ → ℕ → Set where
        i : Roman one one
    {-    v : Roman one five
        x : Roman ten one
    ... -}
        madeup : ∀ {a b} (x : Roman a b) → (c : ℕ) → Roman a c
    
      record Join (A B C : Set) : Set where
        field jo : A → B → C
    
      two : ∀ {a} → Join (Roman a one) (Roman a one) (Roman a (one + one))
      two = record { jo = λ l r → madeup l (one + one) }
    
      _#_ : ∀ {a b c d C} → {{j : Join (Roman a b) (Roman c d) C}} → Roman a b → Roman c d → C
      (_#_) {{j}} = Join.jo j
    
      --   roman = (_#_) {{two}} i i -- works
      roman : Roman one (one + one)
      roman = {! i # i!} -- doesn't work
    
  3. 显然,如果我明确指定隐式,它可以工作 - 所以我相信它不是函数的类型错误。

1 个答案:

答案 0 :(得分:3)

您的示例在Agda的开发版本中运行良好。如果您使用的是早于2.3.2的版本,那么发行说明中的​​这段内容可以澄清为什么它不能为您编译:

* Instance arguments resolution will now consider candidates which
  still expect hidden arguments. For example:

    record Eq (A : Set) : Set where
      field eq : A → A → Bool

    open Eq {{...}}

    eqFin : {n : ℕ} → Eq (Fin n)
    eqFin = record { eq = primEqFin }

    testFin : Bool
    testFin = eq fin1 fin2

  The type-checker will now resolve the instance argument of the eq
  function to eqFin {_}. This is only done for hidden arguments, not
  instance arguments, so that the instance search stays non-recursive.

source

也就是说,在2.3.2之前,实例搜索会完全忽略你的two实例,因为它有一个隐藏的参数。

虽然实例参数的行为有点像类型类,但请注意,如果范围中只有一个类型正确的版本,它们将只提交实例,并且它们不会执行递归搜索:

  Instance argument resolution is not recursive. As an example,
  consider the following "parametrised instance":

    eq-List : {A : Set} → Eq A → Eq (List A)
    eq-List {A} eq = record { equal = eq-List-A }
      where
      eq-List-A : List A → List A → Bool
      eq-List-A []       []       = true
      eq-List-A (a ∷ as) (b ∷ bs) = equal a b ∧ eq-List-A as bs
      eq-List-A _        _        = false

  Assume that the only Eq instances in scope are eq-List and eq-ℕ.
  Then the following code does not type-check:

    test = equal (1 ∷ 2 ∷ []) (3 ∷ 4 ∷ [])

  However, we can make the code work by constructing a suitable
  instance manually:

    test′ = equal (1 ∷ 2 ∷ []) (3 ∷ 4 ∷ [])
      where eq-List-ℕ = eq-List eq-ℕ

  By restricting the "instance search" to be non-recursive we avoid
  introducing a new, compile-time-only evaluation model to Agda.

source


现在,关于问题的第二部分:我不确定你的最终目标是什么,代码的结构最终取决于你构建数字后想要做什么。话虽这么说,我写下了一个小程序,允许你输入罗马数字而不通过显式数据类型(如果我没有明确表达你的意图,请原谅我):

罗马数字将是一个带有一对自然数的函数 - 前一个数字的值和运行总数。如果它比以前的数字小,我们将从运行总数中减去它的值,否则我们将它加起来。我们返回新的运行总数和当前数字的值。

当然,这远非完美,因为没有什么可以阻止我们输入I I X而我们最终将其评估为10.我将此作为练习留给感兴趣的读者。 :)

首先导入(注意我在这里使用标准库,如果你不想安装它,你只需从the online repo复制定义):

open import Data.Bool
open import Data.Nat
open import Data.Product
open import Relation.Binary
open import Relation.Nullary.Decidable

这是我们的数字工厂:

_<?_ : Decidable _<_
m <? n = suc m ≤? n

makeNumeral : ℕ → ℕ × ℕ → ℕ × ℕ
makeNumeral n (p , c) with ⌊ n <? p ⌋
... | true  = n , c ∸ n
... | false = n , c + n

我们可以制作一些数字:

infix 500 I_ V_ X_

I_ = makeNumeral 1
V_ = makeNumeral 5
X_ = makeNumeral 10

接下来,我们必须将这个函数链应用于某个东西,然后提取运行总计。这不是最好的解决方案,但它在代码中看起来不错:

⟧ : ℕ × ℕ
⟧ = 0 , 0

infix 400 ⟦_

⟦_ : ℕ × ℕ → ℕ
⟦ (_ , c) = c

最后:

test₁ : ℕ
test₁ = ⟦ X I X ⟧

test₂ : ℕ
test₂ = ⟦ X I V ⟧

通过test₁评估C-c C-n给我们19test₂然后14

当然,您可以将这些不变量移动到数据类型中,添加新的不变量等等。