shaplessでdeclineのOptsを導出する
shaplessのLabelledGenericを使ってdeclineのOptsを自動導出します。
はじめに
declineではオプション引数ごとに詳細を指定してOptsを生成する必要があります。case classのフィールド名をそのままオプション名にするので十分な場合にはOptsを生成するコードは冗長になります。今回はshapelessを使ってcase classの定義からOptsを導出してみます。
やること
case classの各フィールドから対応するOptsを導出し、case class自体のOptsへ合成します。以下のコードのようなイメージです。
final case class Config(name: String, favoriteNumber:Int) val name = Opts val favoriteNumber = Opts val configOpts = (name, favoriteNumber).mapN(Config.apply)
Optsの導出はshaplessを使った型クラスインスタンスの生成における典型的なパターンです。Optsを直接導出することはできないのでOptsを生成するOptGenを導出します。
import cats.implicits._ import com.monovore.decline.{Argument, Opts} import shapeless.labelled.{FieldType, field} import shapeless.{::, HList, HNil, LabelledGeneric, Lazy, Witness} trait OptGen[A] { def opts: Opts[A] } object OptGen { def apply[A](implicit opts: Lazy[OptGen[A]]): OptGen[A] = opts.value private final case class Gen[A](opts: Opts[A]) extends OptGen[A] implicit def genericOpt[A, R <: HList](implicit generic: LabelledGeneric.Aux[A, R], opts: Lazy[OptGen[R]] ): OptGen[A] = Gen(opts.value.opts.map(generic.from)) implicit def hlistOpt[K <: Symbol, H, T <: HList](implicit hOpts: Lazy[OptGen[FieldType[K, H]]], tOpts: OptGen[T] ): OptGen[FieldType[K, H] :: T] = Gen( (hOpts.value.opts, tOpts.opts).mapN(_ :: _) ) implicit def fieldOpt[K <: Symbol, T](implicit witness: Witness.Aux[K], arg: Argument[T] ): OptGen[FieldType[K, T]] = //ヘルプ文字列は割り切り!! Gen(Opts.option[T](witness.value.name, witness.value.name).map(field[K](_))) implicit val hnilOpt: OptGen[HNil] = Gen(Opts.unit.as(HNil)) }
実行してみる
導出したOptsでのパースも明示的に生成した場合と同様に行えます。
final case class Config(name: String, favoriteNumber:Int) val opts = OptGen[Config].opts val command = Command("opts example", "")(opts) val result = command.parse(Seq("--name","foobar", "--favoriteNumber", "128")) println(result) //Right(Config(foobar,128))
まとめ
いつもながらshapelessは強力だと思います。