7

Scala では、制御フロー指向の方法で考えるという Java/C の習慣を次第に失い、先に進んで興味のあるオブジェクトを最初に取得し、次に通常は amatchまたは amap()またはforeach()forのようなものを適用することに慣れました。コレクション。コードを構成するためのより自然で的確な方法のように感じられるので、とても気に入っています。

少しずつ、条件についても同じようにプログラムできたらいいのにと思いました。つまり、最初にブール値を取得しmatchてから、さまざまなことを行います。ただし、本格的なmatchは、このタスクには少しやり過ぎのようです。

比較:

obj.isSomethingValid match {
  case true => doX
  case false => doY
}

対Javaに近いスタイルで書くもの:

if (obj.isSomethingValid)
  doX
else
  doY

ifTrue:次に、SmalltalkとifFalse:メッセージ (およびその変形)を思い出しました。Scalaでこのようなものを書くことは可能でしょうか?

obj.isSomethingValid ifTrue doX else doY

バリアントあり:

val v = obj.isSomethingValid ifTrue someVal else someOtherVal

// with side effects
obj.isSomethingValid ifFalse {
  numInvalid += 1
  println("not valid")
}

さらに、このスタイルを のような単純な 2 状態の型で使用できるようにすることはできますOptionか? より慣用的な使用方法Optionは、それをコレクションとして扱い、その上で , を呼び出すことfilter()ですが、多くの場合、最後に、定義されている場合は実行し、定義されていない場合は実行したいことがわかります。何かのようなもの:map()exists()doXdoY

val ok = resultOpt ifSome { result =>
  println("Obtained: " + result)
  updateUIWith(result) // returns Boolean
} else {
  numInvalid += 1
  println("missing end result")
  false
}

私には、これは (まだ?) 本格的な よりも見栄えがしますmatch

私が思いついた基本実装を提供しています。このスタイル/テクニックおよび/またはより良い実装に関する一般的なコメントは大歓迎です!

4

3 に答える 3

14

まずelse、 はキーワードであるため、おそらく再利用できません。また、逆引用符を使用して強制的に識別子として表示させるのはかなり見苦しいので、otherwise代わりに使用します。

これが実装の試みです。まず、 pimp-my-library パターンを使用して と を追加ifTrueifFalseますBoolean。これらは戻り値の型Rでパラメーター化され、指定された条件が実現された場合に評価される単一の名前付きパラメーターを受け入れます。しかし、そうする際には、otherwise呼び出しを許可する必要があります。そこでOtherwise0、可能な中間結果をOption[R]. 現在の条件 (ifTrueまたはifFalse) が実現されている場合は定義され、それ以外の場合は空です。

class BooleanWrapper(b: Boolean) {
  def ifTrue[R](f: => R) = new Otherwise0[R](if (b) Some(f) else None)
  def ifFalse[R](f: => R) = new Otherwise0[R](if (b) None else Some(f))
}
implicit def extendBoolean(b: Boolean): BooleanWrapper = new BooleanWrapper(b)

今のところ、これは機能し、書くことができます

someTest ifTrue {
  println("OK")
}

ただし、もちろん、次のotherwise句がなければ、 type の値を返すことはできませんR。の定義は次のOtherwise0とおりです。

class Otherwise0[R](intermediateResult: Option[R]) {
  def otherwise[S >: R](f: => S) = intermediateResult.getOrElse(f)
  def apply[S >: R](f: => S) = otherwise(f)
}

渡された名前付き引数を評価するのは、前の引数から取得した中間結果ifTrueまたはifFalse未定義の場合のみです。これはまさに必要なものです。型のパラメーター化には、名前付きパラメーターの実際の型の最も具体的な共通スーパータイプであると推論される[S >: R]効果がSあります。たとえば、rこのスニペットでは、推論された型がありFruitます。

class Fruit
class Apple extends Fruit
class Orange extends Fruit

val r = someTest ifTrue {
  new Apple
} otherwise {
  new Orange
}

エイリアスを使用すると、コードの短いチャンクに対してメソッド名を完全apply()にスキップすることもできます。otherwise

someTest.ifTrue(10).otherwise(3)
// equivalently:
someTest.ifTrue(10)(3)

最後に、対応するポン引きは次のOptionとおりです。

class OptionExt[A](option: Option[A]) {
  def ifNone[R](f: => R) = new Otherwise1(option match {
    case None => Some(f)
    case Some(_) => None
  }, option.get)
  def ifSome[R](f: A => R) = new Otherwise0(option match {
    case Some(value) => Some(f(value))
    case None => None
  })
}

implicit def extendOption[A](opt: Option[A]): OptionExt[A] = new OptionExt[A](opt)

class Otherwise1[R, A1](intermediateResult: Option[R], arg1: => A1) {
  def otherwise[S >: R](f: A1 => S) = intermediateResult.getOrElse(f(arg1))
  def apply[S >: R](f: A1 => S) = otherwise(f)
}

アンラップされた値を関数引数だけでなく、次の anの関数引数にもOtherwise1便利に渡すことができるようにする必要があることに注意してください。ifSomeotherwiseifNone

于 2011-04-13T18:46:34.183 に答える
6

問題を具体的に見すぎている可能性があります。おそらく、パイプ演算子を使用したほうがよいでしょう。

class Piping[A](a: A) { def |>[B](f: A => B) = f(a) }
implicit def pipe_everything[A](a: A) = new Piping(a)

今、あなたはすることができます

("fish".length > 5) |> (if (_) println("Hi") else println("Ho"))

確かに、これはあなたが達成しようとしているものほどエレガントではありませんが、驚くほど用途が広いという大きな利点があります.(ブール値だけでなく)引数を最初に置きたいときはいつでも、それを使用できます. .

また、すでにオプションを好きなように使用できます。

Option("fish").filter(_.length > 5).
  map (_ => println("Hi")).
  getOrElse(println("Ho"))

これらが戻り値を取る可能性があるからといって、避ける必要があるわけではありません。構文に慣れるには少し時間がかかります。これは、独自の暗黙を作成する正当な理由になる場合があります。しかし、コア機能はそこにあります。(独自のものを作成する場合は、fold[B](f: A => B)(g: => B)代わりに検討してください。慣れれば、介在するキーワードがないことは実際にはかなり良いことです。)


編集:|>パイプの表記はやや標準的ですが、実際にはメソッド名の方が自然に見えるuseため、メソッド名として好みます。def reuse[B,C](f: A => B)(g: (A,B) => C) = g(a,f(a))

于 2011-04-13T19:08:32.017 に答える
2

次のように使用しないでください。

val idiomaticVariable = if (condition) { 
    firstExpression
  } else { 
    secondExpression 
  } 

?

IMO、それは非常に慣用的です!:)

于 2011-04-14T09:12:59.070 に答える