以前チラッと書いた事があったが、Pythonは3.10以降(※1)でパターンマッチング
構文が導入された。
そして恐らく、メジャーなプログラミング言語でパターンマッチングが導入された初めてのケースとなっている。
個人的な印象だが、パターンマッチングはヨーロッパ系の尖った実験言語(といえば語弊があるが)では良く採用されてるが、メジャーなプログラミング言語だと無視され続けてきた、ってカンジだ。
いや、単なる印象論だよ?
パターンマッチングは便利ではあるんだけど、単純な条件分岐に比べると処理の負荷が高い、と言った理由だろう。
しかし元々Pythonは重いし遅い。今更多少負荷がある構文が導入されたから、っつったって大して変わらんだろ(笑)。
いずれにせよ、HaskellやOCaml/F#で培われたパターンマッチングが初めて檜舞台に姿を現した、っちゅーこっちゃ。
そして、殆どの「フツーのプログラマ」が初めて目にする機能となる。
しかしPythonユーザーには邪魔者がいる。
例によってC言語脳だ。
Pytnonのパターンマッチングの構文の見た目は非常にC言語のswitch文に似ている。
C言語のswitch文
これで例によって
「Pythonのmatch文はC言語のswitch文と同じに違いない!」
と言う多大な誤解で概念的に間違った事を流布する可能性が高いんだ。
何故なら彼らはC言語は全ての基礎と言う根拠がない虚言を信じ込んでいて、C言語以外をマジメに学習すると言う事をしないから、だ(※2)。
よってそれに釘を刺しておこうと思った。
確かにPythonのmatch文は単なる条件分岐も出来る。
と言うより、長らくPythonはif文しかなかったんで、長い潜伏期間から新しい条件分岐文を追加したように見えるだろう。
しかし、matchはC言語のswitchみたいに貧弱じゃない。
例を見てみよう。
例えばOCaml/F#での次のようなパターンマッチングは
Pythonでは次のように記述する。
これはRacketで言うと大体次のような意味だ。
Pythonだと、matchは機能的には高級なのに、C言語のswitch文に似せてる辺りが紛らわしいが、caseがパターン記述部分を引き連れている。
なお、Pythonもmatchの次は一つのデータしか取れないが、3, 4のように記述してもそれはタプルだと解釈される、と言うのはお馴染みだろう。
次に、二次元平面の原点から、ある点までの距離を計算する関数の例を見てみる。
OCaml/F#だと次のように書くパターンだ。
これは例えばPythonでは次のように書く。
これもRacketで言うとこういうパターンだ。
いずれにせよ、リスト、あるいはタプルのような合成データ(Compound Data)をmatchで要素毎にバラバラに分解して、任意の計算をかます事が出来る。
C言語のswitch文よか遥かに強力なんだ。
基本はこれだけ、なんだけど、OCaml/F#やRacketだと構造体を分解するパターンはPythonだとどうなるんだろう。
と言うより、Pythonは構造体は持たないが代わりにclassを持ってるので、そのclassをどうやって分解するのか、ってのが気になるだろう。
OCaml/F#だと次のようにしてまずは構造体を定義する。
Racketだと以下のようになるが(※3)。
Pythonだと構造体がない、ので代わりにclassで定義する。
そしてOCaml/F#で以下のように書く時。
Racketでは以下のように書いてたが。
Pythonは変数名を明確にせなアカン、って辺りではむしろOCamlのそれに近くなっている。
こう書けばPythonでもクラスのインスタンス変数をバラバラに分解して計算処理を行う事が出来る。
これ、ホントの事を言うと構造体もそうなんだけど、破壊的変更Welcomeの筈のクラスでも関数型プログラミングが出来る、っちゅーか、そっちに寄せていける機能ではあるんだよな。
つまり、クラスをインスタンス変数毎にバラバラに出来る、って事はここでインスタンスを再構成する事がやりやすくなる、って事だからだ。オブジェクト指向なのに破壊的変更を伴わないプログラミングが行いやすい、って事だ。
これはとんだパラダイム変換になるかもしれん。
とまぁ、Pythonのmatch。
C言語脳には理解し難い機能だろうが、OCaml/F#やSchemeのパターンマッチング拡張に慣れてる層、あるいはC++で構造化代入をやりまくってるには使い勝手が良い機能、と言うより色々と便利なんで使いまくれる新機能だ。
存分に活用して欲しい。
※1: 3.9の次が3.10ってのがメンドイ。素直に4でエエやん。
現在の最新版は3.11になっている。
※2: C++ユーザーはC言語脳には含まれないだろう。
また、C++ユーザーは、C言語に含まれない構造化代入に慣れているので、パターンマッチングの意味をすぐさま理解するだろう。
※3: ちなみに、OCaml版だと*_tと言う名前で構造体を定義してるが、これは知ってる限りOCamlでの習慣じゃないと思う。
C言語で構造体を使う際に、*_tと末尾に付ける命名法が良くあるので、恐らくそれに従ってるんじゃないか。構造体は「ユーザー定義型」を作成する機能なんで、tはtype(型)のtだと思う。
しかし、Lisp系言語ではそのような命名規約は通常ないので、単純に*-tにしてる。
加えると、フツーの言語だとハイフン(-)が使えないだけ、だ。Lispだとハイフンが使えまくるんで、些か不自然なアンダースコア(_)をわざわざ使う必要がない。
なお、Lispで有名な命名規約は、大域変数名をアスタリスク(*)で挟んだり、あるいは同じ大域変数名でも定数のつもりの場合はプラス記号(+)で挟む、等があるが、強制的でもない。