Skip to content

型クラスマジックを紹介する: 任意個の引数を取る関数 #2403

@Seasawher

Description

@Seasawher
/- # 可変個の引数を取る関数

Lean の通常の関数は、型によって引数の個数が決まります。たとえば `Nat → Nat` は 1 引数の関数で、`Nat → Nat → Nat` は 2 引数の関数です。

そのため、可変個の引数を取る関数を実装するときは、次のどちらかの方針を取ることになります。

* 期待される返り値の型から、引数の個数を決める
* 「さらに引数を受け取れる値」を返して、関数のように呼び出せるようにする

まず、期待される返り値の型から引数の個数を決める実装を見てみます。
-/

namespace ExpectedType --#

/-- `α` 型の結果を作るまで、`Nat` 型の引数を足し込んでいくための型クラス -/
class SumArgs (α : Type u) where
  sum : Nat → α

/-
最終的に `Nat` を返すなら、これ以上引数を受け取らずに累積値を返します。
-/
instance : SumArgs Nat where
  sum acc := acc

/-
最終的に `Nat → α` を返すなら、`Nat` 型の引数を 1 つ受け取って、累積値に足してから続きを作ります。
-/
instance [SumArgs α] : SumArgs (Nat → α) where
  sum acc x := SumArgs.sum (acc + x)

/-- 期待される型に応じて、任意個の `Nat` 型の引数を合計する関数 -/
def sumAll {α : Type u} [SumArgs α] : α :=
  SumArgs.sum 0

/- `sumAll` は、期待される型が決まると引数の個数も決まります。 -/

/-- info: ExpectedType.sumAll.{u} {α : Type u} [SumArgs α] : α -/
#guard_msgs in --#
#check sumAll

/-- info: sumAll : Nat -/
#guard_msgs in --#
#check (sumAll : Nat)

/-- info: sumAll : Nat → Nat -/
#guard_msgs in --#
#check (sumAll : Nat → Nat)

/-- info: sumAll : Nat → Nat → Nat -/
#guard_msgs in --#
#check (sumAll : Nat → Nat → Nat)

#guard (sumAll : Nat) = 0
#guard ((sumAll : Nat → Nat) 1) = 1
#guard ((sumAll : Nat → Nat → Nat) 1 2) = 3
#guard ((sumAll : Nat → Nat → Nat → Nat) 1 2 3) = 6

/-
しかし、型注釈なしで `sumAll 1` と書くと失敗します。
Lean はこの時点で、`sumAll` を何引数の関数として使うべきかをまだ決められないからです。
-/

/--
error: Function expected at
  sumAll
but this term has type
  ?m.2

Note: Expected a function because this term is being applied to the argument
  1
-/
#guard_msgs in
#eval sumAll 1

end ExpectedType --#

/- ## 型注釈なしで使える形

型注釈なしで `sumAll 1 2 3` のように書きたい場合、`sumAll` を普通の関数ではなく「関数のように呼び出せる値」として定義する方法があります。

次の `VariadicSum` は、これまでの合計値を持つ型です。
-/

/-- 可変個の `Nat` 型の引数を合計している途中の値 -/
structure VariadicSum where
  value : Nat
  deriving DecidableEq

/-
`#eval` の表示では、ラッパ型の中身ではなく合計値だけを表示します。
-/
instance : Repr VariadicSum where
  reprPrec s _ := repr s.value

/-
`CoeFun` インスタンスを定義すると、`VariadicSum` 型の値を関数のように呼び出せるようになります。

ここでは `Nat` 型の引数を 1 つ受け取って、合計値を増やした `VariadicSum` を返します。
-/
instance : CoeFun VariadicSum (fun _ => Nat → VariadicSum) where
  coe s x := ⟨s.value + x⟩

/-
`Nat` が期待される場所では、`VariadicSum` を `Nat` に変換できるようにしておきます。
-/
instance : Coe VariadicSum Nat where
  coe s := s.value

/-
数値リテラルとの等式を書けるように、`VariadicSum` に対する `OfNat` インスタンスも用意します。
-/
instance (n : Nat) : OfNat VariadicSum n where
  ofNat := ⟨n⟩

/-- 型注釈なしで可変個の `Nat` 型の引数を合計する値 -/
def sumAll : VariadicSum := ⟨0/- `sumAll` は `VariadicSum` 型の値ですが、`CoeFun` によって関数のように呼び出せます。 -/

/-- info: sumAll : VariadicSum -/
#guard_msgs in --#
#check sumAll

/--
info: (fun x => { value := sumAll.value + x }) 1 : VariadicSum
-/
#guard_msgs in --#
#check sumAll 1

#guard sumAll = 0
#guard sumAll 1 = 1
#guard sumAll 1 2 = 3
#guard sumAll 1 2 3 = 6

#eval sumAll
#eval sumAll 1
#eval sumAll 1 2
#eval sumAll 1 2 3

/- 引数はリテラルに限りません。`Nat` 型の式であれば渡せます。 -/

#guard
  let x := 10
  let y := 20
  sumAll x (y + 3) = 33

#eval
  let x := 10
  let y := 20
  sumAll x (y + 3)

/- `Nat` が期待される関数に渡すと、`Coe VariadicSum Nat` によって自然数として使えます。 -/

#guard Nat.succ (sumAll 1 2 3) = 7
#eval Nat.succ (sumAll 1 2 3)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions