我正在学习如何在Agda中实现“类型类”。作为一个例子,我试图实现罗马数字,其中#的组成将进行类型检查。
我不清楚为什么Agda抱怨没有加入的实例(罗马_ _)(罗马_ _)_ - 很明显,它无法找出替代那里的自然数字。
有没有更好的方法来引入没有“构造函数”形式的罗马数字?我有一个构造函数“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
显然,如果我明确指定隐式,它可以工作 - 所以我相信它不是函数的类型错误。
答案 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
给我们19
,test₂
然后14
。
当然,您可以将这些不变量移动到数据类型中,添加新的不变量等等。