ElmでサクッとBuilderパターンをする


基礎からわかる Elmが発売されましたね! Elmのノウハウがかなりマニアックかつ実用的なところが丁寧に書かれていて読み進めるのが楽しいです! 本の中にあった所謂Builerパターンが便利かつElmに慣れていない人にとっては目から鱗だろうなと思って、記事として紹介してみます。

Javaの場合

まずはJavaのケースで考えてみます。ものすごいコアな部分だけ取り出すと、フィールドの値を書き換えてビルダー自身(this)を返しています。ポイントとしてはビルダー自身はミュータブルなアプローチで書き換えて、必要なタイミングで値(Mailer)自身を作成したり、アクション(send)したりします。

public class MailBuilder {
    private String fromAddress = "";

     public MailBuilder from(String address) {
        this.fromAddress = address;
        return this;
    }
    ...
}

Elmの場合

ElmのRecordを利用するアプローチはとってもシンプルで簡単です。RecordはSetter(Update構文)を持っていて変更を反映した新しいRecordを作り出して返すので、Builderを経由せず、かつ、イミュータブルなアプローチで安全に値を生成することができます。

まずは、型の定義とデフォルトのレコードを用意します。

type alias Mailer =
    { fromAddress : String
    , toAddress : String
    , subject : String
    , body : String
    }


defaultMailer : Mailer
defaultMailer =
    Mailer "" "" "" ""

ビルダーのメソッドに当たる部分は、変えたい値を受け取って、RecordのSetterを呼び出すだけです。


from : String -> Mailer -> Mailer
from address mailer =
    { mailer | fromAddress = address }


to : String -> Mailer -> Mailer
to address mailer =
    { mailer | toAddress = address }


subject : String -> Mailer -> Mailer
subject s mailer =
    { mailer | subject = s }


body : String -> Mailer -> Mailer
body b mailer =
    { mailer | body = b }

すると、パイプライン演算子を利用することで、簡単にBuilderパターンをすることができます。パイプは前の関数を評価した値を次の関数の最後の引数に渡す単なる関数です。これはJavaバージョンで見たreturn thisをより直接的に書いた方法になります。また、fromやtoなどのBuilderメソッドの引数をすべて埋めずに第一引数のStringだけ埋めています。これは、Elmの関数がデフォルトでカリー化されており、部分適用しているためです。ここでは詳しい説明は控えておきますが、普段List.mapやfilterなどで自然にやっていることだと思うので、是非マスターしてみてください。

hogeMailer : Mailer
hogeMailer =
    defaultMailer
        |> from "[email protected]"
        |> to "[email protected]"
        |> subject "Greeting"
        |> body "Hello, Mr. Hoge"

ソースコード全体です。

type alias Mailer =
    { fromAddress : String
    , toAddrees : String
    , subject : String
    , body : String
    }


defaultMailer : Mailer
defaultMailer =
    Mailer "" "" "" ""


from : String -> Mailer -> Mailer
from address mailer =
    { mailer | fromAddress = address }


to : String -> Mailer -> Mailer
to address mailer =
    { mailer | toAddreess = address }


subject : String -> Mailer -> Mailer
subject s mailer =
    { mailer | subject = s }


body : String -> Mailer -> Mailer
body b mailer =
    { mailer | body = b }


hogeMailer : Mailer
hogeMailer =
    defaultMailer
        |> from "[email protected]"
        |> to "[email protected]"
        |> subject "Greeting"
        |> body "Hello, Mr. Hoge"