必須引数とオプショナル引数が混在した名前付き引数渡し

Haskellで名前付き引数(named arguments/parameters)のようなことをしようと思ったらレコード構文を活用するのが一般的である。

data Args = Args {
  arg1 :: Int,
  arg2 :: Int,
  arg3 :: String
  }

func :: Args -> Int
func = undefined

main = print $ func $ Args { arg1 = 100, arg2 = 50, arg3 = "foobar" }

この際、デフォルトのArgsレコードを用意してHaskellのレコード更新構文を使うことで、引数をoptionalにすることができる。

defArgs :: Args
defArgs = Args { arg1 = 0, arg2 = 0, arg3 = "" }

main = print $ func $ defArgs { arg2 = 99 }

更新されていないフィールドにはデフォルトの値が用いられる。

data-defaultパッケージへ依存しても構わないのであれば、Default型クラスを使うとより利便性が向上するだろう。

import Data.Default (Default(def))

instance Default Args where
  def = defArgs

main = print $ func $ def { arg2 = 99 }

全ての引数がoptionalであればこれで構わない。しかし、一部の引数を必須(mandatory)としたい場合、どうすればよいだろうか。つまり、

data Args = Args {
  m1 :: Int,
  m2 :: Int,
  o1 :: String
  }

このような引数構成で、m1とm2は必須、o1はオプショナルとしたい場合である。

まず、先述のdefArgsに引数を取らせる方法が考えられる

data Args = Args {
  m1 :: Int,
  m2 :: Int,
  o1 :: String
  }

defArgs :: Int -> Int -> Args
defArgs m1_val m2_val = Args { m1 = m1_val, m2 = m2_val, o1 = "" }

func :: Args -> Int
func = undefined

main = print $ func $ defArgs 4 5

しかしこれでは必須引数が結局positional argumentになってしまい、必須引数が増えた場合に可読性が低下する。

必須引数をundefinedとする手もある。

import Data.Default (Default(def))

data Args = Args {
  m1 :: Int,
  m2 :: Int,
  o1 :: String
  }

instance Default Args where
  def = Args { m1 = undefined, m2 = undefined, o1 = "" }

func :: Args -> Int
func = undefined

main = print $ func $ def { m1 = 10, m2 = 20 }

こうしておけば、必須引数を設定し忘れた場合、その値を参照しようとした段階で実行時例外が発生する。

しかし、この方法では必須引数の設定し忘れをコンパイル時に検出できない。Haskellを書いている以上、プログラムの正しさはなるべくコンパイル時に検証したいものである。

そうなると、やはり必須引数とoptional引数を分けたほうがいいのだろうか。

import Data.Default (Default(def))

data Args = Args {
  m1 :: Int,
  m2 :: Int
  }

data Opts = Opts {
  o1 :: String
  }

instance Default Opts where
  def = Opts { o1 = "" }

func :: Args -> Opts -> Int
func = undefined

main = print $ func Args { m1 = 10, m2 = 20 } def

上の例では、必須引数をArgs型に、optional引数をOpts型にまとめ、Opts型のみをDefaultのインスタンスにしている。また、func関数はこの2つを引数としてとる。Args型データをレコードコンストラクタで作成する際にフィールドの数が足りない場合、ghcは警告を発する。