パターンマッチング
💡 お知らせ: このドキュメントはAIによって翻訳されています。表現に違和感がある場合は、原文(英語)を参照するか、翻訳にご協力ください。
Enum に対するマッチング
Flix は、代数的データ型(algebraic data type)に対するパターンマッチングを サポートしています。
たとえば、図形をモデル化する代数的データ型があるとします。
enum Shape {
case Circle(Int32)
case Square(Int32)
case Rectangle(Int32, Int32)
}
このとき、パターンマッチングを使って Shape の面積を計算する関数を、
次のように書けます。
def area(s: Shape): Int32 = match s {
case Shape.Circle(r) => 3 * (r * r)
case Shape.Square(w) => w * w
case Shape.Rectangle(h, w) => h * w
}
レコードに対するマッチング
上記はレコード型に対しても機能しますが、構文が少し異なります。先ほどの
Shape 型を、今度はレコードを使って書き換えてみましょう。
enum Shape {
case Circle({ radius = Int32 })
case Square({ width = Int32 })
case Rectangle({ height = Int32, width = Int32 })
}
def area(s: Shape): Int32 = match s {
case Shape.Circle({ radius }) => 3 * (radius * radius)
case Shape.Square({ width }) => width * width
case Shape.Rectangle({ height, width }) => height * width
}
上の例では、各パターンが指定されたラベルをちょうど持つことを、暗黙に 要求しています。多すぎても少なすぎてもいけません。 しかし一般に、レコードパターンの構文はその型の構文と似ています。 そのため、少なくとも特定のラベルを 1 つ持つレコードにマッチさせることができます。
def f(r: { height = Int32 | a }): Int32 = match r {
case { height | _ } => height
// 拡張部分は使われないのでワイルドカードパターンになっています
}
ただし、パターンは型も規定する(imply)ことに注意してください。そのため、 次の例は動作しません。
def badTypes(r: { height = Int32 | a }): Int32 = match r {
case { height } => height
}
さらに、すべての case は同じ型でなければならないため、これも動作しません。
match ??? {
case { height | _ } => height
case { height } => height
}
これはやや不自然な(こじつけのような)例かもしれませんが、よくある落とし穴を示しており、 簡単に修正できます。
これは、最初の case が height ラベルを持つ多相的なレコード(polymorphic
record)であるのに対し、2 番目の case が height ラベル のみ が定義された
閉じたレコード(closed record)にマッチするためです。
さらに、{ label } パターンは実際には { label = pattern } の
糖衣構文(syntactic sugar)です。そのため、複数のレコードを扱う場合は、
異なるパターンを使う必要があるかもしれません。
def shadowing(r1: { height = Int32 | a }, r2: { height = Int32 | b }): Int32 =
match (r1, r2) {
case ({ height | _ }, { height | _ }) => height + height
// `height = height` が 2 回定義されるため、これは動作しません
}
しかし、変数の名前を変えれば、プログラムは型検査を通ります。
def renaming(r1: { height = Int32 | a }, r2: { height = Int32 | b }): Int32 =
match (r1, r2) {
case ({ height = h1 | _ }, { height = h2 | _ }) => h1 + h2
}
まとめると、レコードパターンの例をいくつか挙げます。
{ }- 空のレコード{ radius = r }-radiusラベルだけを持つレコード。その値はスコープ内でrに束縛されます{ radius }-radiusラベルだけを持つレコード(これは実際には{ radius = radius }の糖衣構文です){ radius | _ }- 少なくともradiusラベルを持つレコード{ radius | r }- 少なくともradiusラベルを持つレコード。レコードの残りの部分はrに束縛されます
let パターンマッチ
パターン match 構文に加えて、let 束縛(let-binding)を使って値を分解する
こともできます。たとえば:
let (x, y, z) = (1, 2, 3)
これは変数 x、y、z を、それぞれ値 1、2、3 に束縛します。
網羅的(exhaustive)なパターンであれば、let 束縛の中で使えます。たとえば:
let (x, Foo(y, z)) = (1, Foo(2, 3))
これは、Foo がその型における唯一のコンストラクタである場合に限り、
正当です。
次の let 束縛は網羅的でないため 不正 です。
let (1, 2, z) = ...
let Some(x) = ...
Flix コンパイラは、このような網羅的でないパターンを拒否します。
let パターンマッチはレコードと相性が良く、レコードを分解して、関心のある ラベルだけを使うことができます。
let { height | _ } = r;
height + height
マッチラムダ
パターンマッチは、ラムダ式とともに使うこともできます。たとえば:
List.map(match (x, y) -> x + y, (1, 1) :: (2, 2) :: Nil)
は次と等価です。
List.map(w -> match w { case (x, y) => x + y }, (1, 1) :: (2, 2) :: Nil)
let 束縛の場合と同様に、このようなパターンマッチも網羅的でなければなりません。
2 つのラムダ式の違いに注意してください。
let f = (x, y, z) -> x + y + z + 42i32
let g = match (x, y, z) -> x + y + z + 42i32
ここで f は 3 つ の Int32 引数を取る関数であるのに対し、g は 1 つ の
3 要素タプル (Int32, Int32, Int32) を引数に取る関数です。