6

私は Shapeless 2.0 を使用しており、HList を使用して入力を検証しようとしています — コンパイル時に可能な限り多くのチェックを実行します。

想定している入力の型を指定する HListspecがあり (型はコンパイル時にチェックする必要があります)、実行する実行時チェックも含めることができます (たとえば、数値が偶数か奇数かをテストするため)。

次の仕様を検討してください。

trait Pred[T] { def apply(t: T): Boolean }
val IsString = new Pred[String] { def apply(s: String) = true }
val IsOddNumber = new Pred[Int] { def apply(n: Int) = n % 2 != 0 }
val IsEvenNumber = new Pred[Int] { def apply(n: Int) = n % 2 == 0 }
val spec = IsEvenNumber :: IsString :: IsString :: IsOddNumber :: HNil

そして、さまざまなサンプル入力:

val goodInput = 4 :: "foo" :: "" :: 5 :: HNil
val badInput = 4 :: "foo" :: "" :: 4 :: HNil
val malformedInput = 4 :: 5 :: "" :: 6 :: HNil

効果的に実行できる関数を作成するにはどうすればよいですか:

input.zip(spec).forall{case (input, test) => test(input)}

したがって、次のようになります。

f(spec, goodInput) // true
f(spec, badInput) // false
f(spec, malformedInput) // Does not compile
4

1 に答える 1

7

Travis Brown によるこれらの回答には、必要なもののほとんどが含まれています。

しかし、それらの答えを見つけ、それらがあなたの問題に適用可能であることを理解し、それらを組み合わせて適用する詳細を考え出すのに長い時間がかかりました.

そして、あなたの質問は、実際の問題、つまり入力の検証を解決するときにこれがどのように発生するかを示しているため、価値があると思います。また、デモ コードとテストを含む完全なソリューションを示すことで、以下に価値を追加しようとします。

チェックを行うための一般的なコードは次のとおりです。

object Checker {
  import shapeless._, poly._, ops.hlist._
  object check extends Poly1 {
    implicit def apply[T] = at[(T, Pred[T])]{
      case (t, pred) => pred(t)
    }
  }
  def apply[L1 <: HList, L2 <: HList, N <: Nat, Z <: HList, M <: HList](input: L1, spec: L2)(
    implicit zipper: Zip.Aux[L1 :: L2 :: HNil, Z],
             mapper: Mapper.Aux[check.type, Z, M],
             length1: Length.Aux[L1, N],
             length2: Length.Aux[L2, N],
             toList: ToList[M, Boolean]) =
    input.zip(spec)
      .map(check)
      .toList
      .forall(Predef.identity)
}

デモの使用コードは次のとおりです。

object Frank {
  import shapeless._, nat._
  def main(args: Array[String]) {
    val IsString     = new Pred[String] { def apply(s: String) = true       }
    val IsOddNumber  = new Pred[Int]    { def apply(n: Int)    = n % 2 != 0 }
    val IsEvenNumber = new Pred[Int]    { def apply(n: Int)    = n % 2 == 0 }
    val spec = IsEvenNumber :: IsString :: IsString :: IsOddNumber :: HNil
    val goodInput       = 4 :: "foo" :: "" :: 5 :: HNil
    val badInput        = 4 :: "foo" :: "" :: 4 :: HNil
    val malformedInput1 = 4 :: 5     :: "" :: 6 :: HNil
    val malformedInput2 = 4 :: "foo" :: "" :: HNil
    val malformedInput3 = 4 :: "foo" :: "" :: 5 :: 6 :: HNil
    println(Checker(goodInput, spec))
    println(Checker(badInput, spec))
    import shapeless.test.illTyped
    illTyped("Checker(malformedInput1, spec)")
    illTyped("Checker(malformedInput2, spec)")
    illTyped("Checker(malformedInput3, spec)")
  }
}

/*
results when run:
[info] Running Frank
true
false
*/

illTypedを使用して、コンパイルすべきではないコードがコンパイルされないことを確認することに注意してください。

いくつかの補足事項:

  • 私は当初、これを使って長い庭の道をたどりました。ここでは、すべての場合の戻り値の型がブール型であることを表すために、多相関数checkが よりも具体的な型を持つことが重要だと考えました。Poly1だから私はそれを で動作させようとし続けましたextends (Id ~>> Boolean)。しかし、結果の型がすべての場合にブール型であることを型システムが認識しているかどうかは問題ではありません。私たちが実際に持っている唯一のケースが正しい型を持っていれば十分です。extends Poly1は素晴らしいことです。
  • 値レベルzipは伝統的に、長さが等しくないことを許可し、エクストラを破棄します。Miles は Shapeless の type-levelzipでこれに続いたので、長さが等しいかどうかを個別にチェックする必要があります。
  • 呼び出しサイトが でなければならないのは少し悲しいことです。import nat._そうしないと、 の暗黙のインスタンスLengthが見つかりません。これらの詳細は、定義サイトで処理することをお勧めします。(修正は保留中です。 )
  • 私が正しく理解していれば、長さチェックを回避するためにMapped(a la https://stackoverflow.com/a/21005225/86485IsString ) を使用することはできません。例えばPred[String]
  • PredTravis は、それを拡張T => Booleanして使用できるようにする可能性があると指摘していますZipApply。読者の演習として、この提案に従っておきます:-)
于 2014-03-20T21:26:05.920 に答える