2

次のコードが与えられます:

case class Config(
  addThree: Boolean = true,
  halve: Boolean = true,
  timesFive: Boolean = true
)


def doOps(num: Integer, config: Config): Integer = {
  var result: Integer = num
  if ( config.addThree ) {
    result += 3
  }
  if ( config.halve ) {
    result /= 2
  }
  if ( config.timesFive ) {
    result *= 5
  }
  result
}                                             

val config = Config(true,false,true)          

println( doOps(20, config) )
println( doOps(10, config) )

醜いdoOpsメソッドをより効率的で慣用的な構成に置き換えたいと思います。具体的には、使用されている特定の構成に基づいて、必要な変換のみを実行する関数のチェーンを構築したいと思います。整数を渡すことができる、ある種の部分的に適用された関数を作成したいと思うかもしれませんが、これを効率的に実現する方法については空白を描いています。

特に、doOps内のifステートメントを避けたいので、結果の構造体を、最初に条件をチェックせずにチェーン内の次の関数を呼び出す関数のチェーンにします。

結果のコードは、次のようになると思います。

case class Config(
  addThree: Boolean = true,
  halve: Boolean = true,
  timesFive: Boolean = true
)

def buildDoOps(config: Config) = ???

val config = Config(true,false,true)
def doOps1 = buildDoOps(config)

println( doOps1(20) )
println( doOps1(10) )
4

4 に答える 4

3

これが私の提案です。基本的に、私は互いに独立した一連の関数を作成します。いずれかの操作が無効になっている場合は、に置き換えますidentity。最後に、引数を初期値としてfoldLeft使用して、そのシーケンスを確認します。num

case class Config(
  addThree: Boolean = true,
  halve: Boolean = true,
  timesFive: Boolean = true
) {

  private val funChain = Seq[Int => Int](
    if(addThree) _ + 3 else identity _,
    if(halve) _ / 2 else identity _,
    if(timesFive) _ * 5 else identity _
  )

  def doOps(num: Int) = funChain.foldLeft(num){(acc, f) => f(acc)}

}

そこにぴったり収まるようにdoOps()中に入れました。Config

Config(true, false, true).doOps(10)  //(10 + 3 ) * 5 = 65

あなたがマゾヒストなら、次のfoldLeft()ように書くことができます:

def doOps(num: Int) = (num /: funChain){(acc, f) => f(acc)}

気に入らない場合は、とidentityを使用Option[Int => Int]してflattenください:

private val funChain = Seq[Option[Int => Int]](
    if(addThree) Some(_ + 3) else None,
    if(halve) Some(_ / 2) else None,
    if(timesFive) Some(_ * 5) else None
).flatten
于 2013-02-15T18:00:30.503 に答える
2

Tomasz Nurkiewiczのソリューションに似ていますが、自己準同型(同じ入力と出力タイプを持つ関数)にScalazのモノイドを使用します。

モノイドの追加操作はcomposeであり、単位元はidentity関数です。

import scalaz._, Scalaz._

def endo(c: Config): Endo[Int] =
  c.timesFive ?? Endo[Int](_ * 5) |+|
  c.halve ?? Endo[Int](_ / 2) |+|
  c.addThree ?? Endo[Int](_ + 3)

def doOps(n: Int, c: Config) = endo(c)(n)

??演算子は、左のオペランドが。の場合は右のオペランドを返し、。trueの場合はモノイドの単位元を返しますfalse

関数の構成の順序は、適用される順序と逆であることに注意してください。

于 2013-02-15T18:17:18.067 に答える
0

Config次のように、caseクラスに機能を追加するだけで済みます。これにより、前述のように関数呼び出しを連鎖させることができます。

case class Config(
  doAddThree : Boolean = true,
  doHalve : Boolean = true,
  doTimesFive : Boolean = true
) {
  def addThree(num : Integer) : Integer = if(doAddThree) (num+3) else num
  def halve(num : Integer) : Integer = if(doHalve) (num/2) else num
  def timesFive(num : Integer) : Integer = if(doTimesFive) (num*5) else num
}


def doOps(num: Integer, config: Config): Integer = {
  var result: Integer = num
  result = config.addThree(result)
  result = config.halve(result)
  result = config.timesFive(result)
  result
}                                             

val config = Config(true,false,true)          

def doOps1(num : Integer) = doOps(num, config)

println( doOps1(20) )
println( doOps1(10) )

この「連鎖」を行うためのよりクリーンな方法foldLeftは、他の回答の1つが述べているのと同様に、部分的に適用された関数のリストを使用することです。

def doOps(num: Integer, config: Config): Integer = {
  List(
    config.addThree(_),
    config.halve(_),
    config.timesFive(_)
  ).foldLeft(num) {
    case(x,f) => f(x)
  }
}
于 2013-02-15T18:09:46.470 に答える
0

より宣言的な(そして拡張可能な)スタイルに移行したい場合は、次のようにすることができます。

import collection.mutable.Buffer

abstract class Config {
  protected def Op( func: Int => Int )( enabled: Boolean) {
    if ( enabled ) {
      _ops += func
    }   
  }
  private lazy val _ops = Buffer[Int => Int]()
  def ops: Seq[Int => Int] = _ops
}

def buildDoOps(config: Config): Int => Int = {
  val funcs = config.ops
  if ( funcs.isEmpty ) identity // Special case so that we don't compose with identity everytime
  else funcs.reverse.reduceLeft(_ andThen _)
}

これで、次のように構成を簡単に定義できます。

case class MyConfig(
  addThree: Boolean = true,
  halve: Boolean = true,
  timesFive: Boolean = true
) extends Config {
  Op(_ + 3)(addThree)
  Op(_ / 3)(halve)
  Op(_ * 5)(timesFive)
}

そして最後に、REPLでのテストをいくつか示します。

scala> val config = new MyConfig(true,false,true)
config: MyConfig = MyConfig(true,false,true)
scala> val doOps1 = buildDoOps(config)
doOps1: Int => Int = <function1>
scala> println( doOps1(20) )
115
scala> println( doOps1(10) )
65    

抽象であるbuildDoOpsのインスタンスを取ることに注意してください。つまり、上記のようなConfigサブクラスで機能し、別のタイプの構成を作成するときに書き直す必要はありません。ConfigMyConfig

また、buildDoOps要求された操作のみを実行する関数を返します。つまり、関数を適用するたびに(ただし、関数を作成する場合にのみ)、構成内の値に対して不必要にテストする必要はありません。実際、関数が構成の状態にのみ依存していることを考えると、次のように単純に定義することができます(おそらくそうすべきlazy valですConfigresult

abstract class Config {
  protected def Op( func: Int => Int )( enabled: Boolean) {
    if ( enabled ) {
      _ops += func
    }   
  }
  private lazy val _ops = Buffer[Int => Int]()
  def ops: Seq[Int => Int] = _ops
  lazy val result: Int => Int = {
    if ( ops.isEmpty ) identity // Special case so that we don't compose with identity everytime
    else ops.reverse.reduceLeft(_ andThen _)
  }
}    

次に、次のことを行います。

case class MyConfig(
  addThree: Boolean = true,
  halve: Boolean = true,
  timesFive: Boolean = true
) extends Config {
  Op(_ + 3)(addThree)
  Op(_ / 3)(halve)
  Op(_ * 5)(timesFive)
}

val config = new MyConfig(true,false,true)
println( config.result(20) )
println( config.result(10) )
于 2013-02-15T18:45:39.757 に答える