5

フィルタリングしたい結果がリストにあります。

ユーザーは、行の任意の属性に特定の制限を指定できます(たとえば、x == 1の行のみを表示したい)。制限が指定されていない場合は、もちろんその述語は使用されません。もちろん、これの最も単純な形式は次のとおりです。

list.filter(_.x == 1)

考えられる単純な述語はたくさんあります。ユーザーの検索用語(Option [Int]など)を述語関数またはIdentity(trueを返す関数)に変換するコードを使用して、新しい述語関数をその場で構築しています。コードは次のようになります(わかりやすくするために明示的な型が追加され、短縮されています)。

case class ResultRow(x: Int, y: Int)

object Main extends App {
  // Predicate functions for the specific attributes, along with debug output
  val xMatches = (r: ResultRow, i: Int) => { Console println "match x"; r.x == i }
  val yMatches = (r: ResultRow, i: Int) => { Console println "match y"; r.y == i }
  val Identity = (r : ResultRow) => { Console println "identity"; true }

  def makePredicate(a: Option[Int], b: Option[Int]) : ResultRow => Boolean = {
    // The Identity entry is just in case all the optional params are None 
    // (otherwise, flatten would cause reduce to puke)
    val expr = List(Some(Identity), 
                    a.map(i => xMatches(_: ResultRow, i)),
                    b.map(i => yMatches(_: ResultRow, i))
                   ).flatten

    // Reduce the function list into a single function. 
    // Identity only ever appears on the left...
    expr.reduceLeft((a, b) => (a, b) match {
      case (Identity, f) => f
      case (f, f2) => (r: ResultRow) => f(r) && f2(r)
    })
  }

  val rows = List(ResultRow(1, 2), ResultRow(3, 100))

  Console println rows.filter(makePredicate(Some(1), None))
  Console println rows.filter(makePredicate(None, None))
  Console println rows.filter(makePredicate(None, Some(100)))
  Console println rows.filter(makePredicate(Some(3), Some(100)))
}

これは完全に機能します。実行すると、適切にフィルタリングされ、デバッグ出力は、リストを適切にフィルタリングするために最小限の数の関数が呼び出されたことを証明します。

match x
match x
List(ResultRow(1,2))
identity
identity
List(ResultRow(1,2), ResultRow(3,100))
match y
match y
List(ResultRow(3,100))
match x
match x
match y
List(ResultRow(3,100))

私は実際、これがどれほどうまくいったかについて非常に満足しています。

しかし、私はそれを行うためのより機能的な方法があると思わずにはいられません(例えば、モノイドとファンクターと一般化された合計)...しかし、それを機能させる方法を理解することはできません。

暗黙のゼロと半群を作成する必要があることを示すscalazの例に従ってみましたが、タイプチェックするためにZero [ResultRow=>Boolean]を取得できませんでした。

4

2 に答える 2

4

forall次の方法を使用して、コードを(Scalazに移動せずに)少し単純化できます。

def makePredicate(a: Option[Int], b: Option[Int]): ResultRow => Boolean = {
  val expr = List(
    a.map(i => xMatches(_: ResultRow, i)),
    b.map(i => yMatches(_: ResultRow, i))
  ).flatten

  (r: ResultRow) => expr.forall(_(r))
}

これにより、リストに含める必要もなくなることに注意してくださいSome(Identity)

行が多い場合は、次のように関数をユーザー入力とzip一致させるために使用することをお勧めします。xMatches

val expr = List(a, b) zip List(xMatches, yMatches) flatMap {
  case (maybePred, matcher) => maybePred.map(i => matcher(_: ResultRow, i))
}

2行ではそれほど簡潔でも読みやすくもなりませんが、4行または5行で読みやすくなります。


Scalazに関する質問に答えるには、問題は2つの可能なモノイドがあることですBoolean。Scalazは1つを選択しません。代わりに、ブール値にHaskellのnewtypeラッパーなどのタグを付けて、使用するモノイドを指定する必要があります( Scalaz 7では— 6ではアプローチが少し異なります)。

必要なモノイドを指定するとBoolean、のモノイドが起動し、何もする必要がなくなります。ゼロを明示的Function1に定義する必要はありません。Identity例えば:

import scalaz._, Scalaz._

def makePredicate(a: Option[Int], b: Option[Int]): ResultRow => Boolean =
  List(a, b).zip(List(xMatches, yMatches)).flatMap {
    case (maybePred, matcher) =>
      maybePred.map(i => matcher(_: ResultRow, i).conjunction)
  }.suml

ResultRow => Boolean @@ Conjunctionここでは、関数の合計を取得しました。

于 2012-12-22T12:32:14.613 に答える
3

私が非常に気に入っている単純化の1つは、標準のブール式を述語に引き上げるFunction1 [A、Boolean]のライブラリポン引きを使用して、この種の述語の配管を単純化することです。これが私のサブセットです:

  implicit def toRichPredicate[A](f: Function1[A, Boolean]) = new RichPredicate(f)
  def tautology[A] = (x:A)=>true
  def falsehood[A] = (x:A)=>false

  class RichPredicate[A](f: Function1[A, Boolean]) extends Function1[A, Boolean] {
    def apply(v: A) = f(v)

    def &&(g: Function1[A, Boolean]): Function1[A, Boolean] = {
      (x: A) => f(x) && g(x)
    }

    def ||(g: Function1[A, Boolean]): Function1[A, Boolean] = {
      (x: A) => f(x) || g(x)
    }

    def unary_! : Function1[A, Boolean] = {
      (x: A) => !f(x)
    }
  }

私はこれをすべての地獄のように再利用可能だと思います。そのようなもので、あなたの削減は

list.flatten.foldLeft(tautology)(&&)

これは非常に簡単です。また、トートロジーと&&の述語が明らかにモノイドを形成するため、より深い機能的善への道を示します。したがって、これはすべて、ScalazまたはHaskellの高階型クラスyの善への呼び出しに崩壊します。また、他の状況では、偽りと||によって形成された述語に対してモノイドを使用する可能性があるため、どちらの場合も少し注意が必要です。したがって、オーバーロードされたインスタンス解決が必要になります。

于 2012-12-22T13:17:02.877 に答える