LoginSignup
14
10

More than 5 years have passed since last update.

パターンマッチまとめ

Posted at

オフラインリアルタイムどう書く勉強会第8回に参加してきました。
私は「一般的な解き方で、奇を衒わずに」とか思って解いたらそんな解き方してるのが私だけだったりして寂しかったです。

それで

私がRubyでミューテーションありありで必死こいて解いてる横で、Haskellで颯爽と解いてる方がいらっしゃって非常に悔しかったです。
なので、私もHaskellで解いてみました。

というか、まずScalaで解いて、そのアルゴリズムをそのままHaskellに持ってっただけだったのですが。
書き方が不味かったのでScalaで全テストケース通すのに15sかかったりしましたけど私は元気です。

で、ScalaからHaskellへ移植(?)をしていて気付いたのですが、パターンマッチの書き方に結構差がありますね。
それなら、HaskellとOCamlとの間は、あるいはErlangと比較するとどうなんだろう? と思った次第です。

まとめ(ここだけ読めば良いです)

こんな感じでした。

Scala

abstract sealed class Hoge
case class Foo(s: String) extends Hoge
case class Bar(i: Int) extends Hoge
case object Baz extends Hoge

hoge match {
  case Foo(s) if s == "answer" => 420 // Foo("answer") でも良い
  case _: Foo => 0
  case Bar(i) if i == 42 => 42 // 同じく、Bar(42)でも良い
  case _: Bar => 1
  case Baz => 101
}

よく見るScalaのコードです。文字数が多いですね。
どうでもいい事ですが、 match式は1行で書けます。

hoge match { case Foo(s) if s == "answer" => 420 case _: Foo => 0 case Bar(i) if i == 42 => i case _: Bar => 1 case Baz => 101 }

あと、Scalaのパターンマッチは決まった形の unapply メソッドが定義されたオブジェクトであれば何でも使えるので、独自に拡張するのが容易です。
(容易だからといって多用するのが良いというわけではありませんが)

Haskell

data Hoge = Foo String | Bar Int | Baz deriving Show

case hoge of
  Foo s
    | s == "answer" -> 420
    | otherwise -> 0
  Bar i
    | i == 42 -> 42
    | otherwise -> 1
  Baz -> 101

簡潔で見やすいですね。
値コンストラクタ毎にまとめられる点も好印象です。
無理矢理何か言うとしたら、 otherwise って長すぎない……? くらいでしょうか。
とボヤいたら、同僚の方に「Trueでもいける」って教えてもらいました。

case hoge of
  Foo s
    | s == "answer" -> 420
    | True -> 0

なんだかErlangのif式みたい……。

ちなみにHaskellのcase式も1行で書けます。

case hoge of Foo s | s == "answer" -> 420 | otherwise -> 0; Bar i | i == 42 -> i | otherwise -> 1; Baz -> 101

セミコロンが入ってる時点で 1行 と呼んで良いものか怪しいところですが……。

OCaml

type hoge = Foo of string | Bar of int | Baz;;

match hoge with
    Foo s when s = "answer" -> 420 (* Foo "answer" でも良い *)
  | Foo _ -> 0
  | Bar i when i == 42 -> 42 (* 同じく、Bar 42 でも良い *)
  | Bar _ -> 1
  | Baz -> 101;;

Haskellと似ているようでちょっと違うのがキュートですね。
これぞパターンマッチ、という感じがするのは私だけでしょうか。
比較演算子が = だったりして( == は参照比較)戸惑ったりしました。
これも当然1行で書けますが省略。

Erlang

-record(foo, {s}).
-record(bar, {i}).

case Hoge of
  #foo{s=S} when S == "answer" -> 420;
  #foo{} -> 0;
  #bar{i=I} when I == 42 -> 42;
  #bar{} -> 1;
  baz -> 101
end.

誕生から30年弱にして我が世の春を迎えようとしているErlang.
D言語にもそんな時代が来ないかしらん。

「Erlangには文法に気を使わない人によって設計された言語の臭いがする」というような話もありますが、確かに他の言語から入っていくととっつきにくい文法ですね。
変数名が大文字始まり、小文字だと即ちatom、という規則は慣れていないと嵌ります。関数の引数に間違ってatomを書いちゃったりして、パターンマッチと判断されてエラーも出ないけど呼び出されもしないなど。もっともこの辺りは、慣れの問題ですね。

全体的に宣言的で、文の区切りがフルストップ( . )というProlog仕様。これ自体は結構好きです。やっぱり文の終わりにはフルストップ打たないと(数式も文なので、本来はフルストップ打つべきなど。 ex. 21 * 2 = 42.)。

それはともかくとして。パターンマッチの形としてはOCamlに近い、見慣れた形ですね。

レコードと言うのは、実態は(先頭がレコード名のatomの)タプルで、それを構造体っぽく扱う糖衣構文が用意されてるだけみたいです。
なので、

> case {foo, ""} of #foo{} -> 42 end.
42
> case #foo{s=""} of {foo, _} -> 42 end.
42

と、手動で作る事もできます。

Elixir

defmodule Foo do
  defstruct s: nil
end

defmodule Bar do
  defstruct i: nil
end

case hoge do
  %Foo{s: s} when s == "answer" -> 420
  %Foo{} -> 0
  %Bar{i: i} when i == 42 -> 42
  %Bar{} -> 1
  :baz -> 101
end

Elixirにもレコードはありますが、Structの方が好んで使われているようなので(?)、Structで書いてみました。

私はRubyistなので書きやすいなぁと感じます。
ちょっと文字数が多くなってしまう感は否めないですけど。

まとめ

みんなちがってみんないい

なんて言うと思ったか。
Haskellの簡便さは羨ましい限りですね。
OCamlは基本に忠実感が良い。
Erlangは、あの独特な文法に馴染む事ができれば大変楽しく書けそうですね。馴染めなかったら……。
Elixirも良いです。Erlangに馴染めなかったらElixirを使うという手も。まぁElixirを本格的に使うと半分くらいはErlangと格闘する時間に充てなきゃいけないそうですが。

Scalaだけ、 when じゃなくて if だったり、アローの形が違ったりするのは何故?

おまけ

とか言ってたら、Rustでも同じだよと教えてもらいました。

enum Hoge {
  Foo(String)
}

match hoge {
  Hoge::Foo(ref s) if s == "answer" => 420,
  Hoge::Foo(_) => 0,
};

おお、Scalaと同じような形式だ。

他にも、Clojureでは core.match というライブラリを利用する事でパターンマッチが使えるようになるとも教えてもらいました。ただしレコードの型での振り分けはできないとか。Clojureは詳しくないのでよくわかりません……。

14
10
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
10