4

私は、典型的な例のように、Lisp、Python、JavaScriptなどの(通常は動的な)言語の条件ではなく、ブール演算子を使用して記述できる簡潔なコードが好きです。

x = someString or "default string"

vs

if someString:
    x = someString
else:
    x = "default string"

Scalaで私は次のようなことを考えました:

object Helpers {
  case class NonBooleanLogic[A](x: A) {
    // I could overload the default && and ||
    // but I think new operators are less 'surprise prone'
    def |||(y: => A)(implicit booleanConversion: A => Boolean) = if (x) x else y
    def &&&(y: => A)(implicit booleanConversion: A => Boolean) = if (!x) x else y
  }

  implicit def num2bool(n : Int) = n != 0

  implicit def seq2bool(s : Seq[Any]) = !s.isEmpty

  implicit def any2bool(x : Any) = x != null

  implicit def asNonBoolean[T](x: T) = NonBooleanLogic(x)
}

object SandBox {
  // some tests cases...

  1 ||| 2                                         //> res2: Int = 1

  val x : String = null                           //> x  : String = null
  x ||| "hello"                                   //> res3: String = hello

  //works promoting 2 to Float
  1.0 &&& 2                                       //> res4: Double = 2.0

  //this doesn't work :(
  1 &&& 2.0
}

しかし、2つの懸念が生じます。

  1. タイプに戻らずに、共通の祖先を持つタイプで機能させるにはどうすればよいAnyですか?
  2. これは非常にクールなので、おそらくより適切に文書化され、テストされた包括的なライブラリで、他の誰かが以前にそれを行ったことがあるはずです。どこにありますか?
4

3 に答える 3

3

私は Option[T]... に固執します... これは Scala にとってより慣用的です。また、検証でもよく使用しました。例: Web フォームでは、空の文字列を有効なユーザー入力と見なすべきではない場合があります。

たとえば、null String / String with zero length( "") が false であり、null 参照も false であり、数値ゼロが false であると考える場合、次の暗黙的な定義を記述できます。

object MyOptionConverter
{
    implicit def toOption(any: AnyRef) = Option(any)
    implicit def toOption(str: String) = {
        Option(str).filter(_.length > 0)
    }

    implicit def toOption[T](value: T)(implicit num: Numeric[T]): Option[T] = {
        Option(value).filter(_ != 0)
    }
}

import MyOptionConverter._

println(1 getOrElse 10)   // 1
println(5.5 getOrElse 20) // 5.5
println(0 getOrElse 30)  // 30
println(0.0 getOrElse 40) // 40
println((null: String) getOrElse "Hello")  // Hello
println((null: AnyRef) getOrElse "No object") // No object
println("World" getOrElse "Hello")

また、独自の演算子を本当に定義する必要がある場合は、それを Option[T] を保持するクラスに変換し、それに演算子を追加します。

object MyOptionConverter
{
    class MyBooleanLogic[T](x: Option[T], origin: T)
    {
        def |||(defaultValue: T) = x.getOrElse(defaultValue)
        def &&&(defaultValue: T) = x.isDefined match {
            case true  => defaultValue
            case false => origin
        }
    }

    implicit def toOption(any: AnyRef) = {
        new MyBooleanLogic(Option(any), any)
    }
    implicit def toOption(str: String) = {
        new MyBooleanLogic(Option(str).filter(_.length > 0), str)
    }

    implicit def toOption[T](value: T)(implicit num: Numeric[T])= {
        new MyBooleanLogic(Option(value).filter(_ != 0), value)
    }
}

import MyOptionConverter._

println(1 ||| 10)   // 1
println(5.5 ||| 20) // 5.5
println(0 ||| 30)  // 30
println(0.0 ||| 40) // 40
println((null: String) ||| "Hello")  // Hello
println((null: AnyRef) ||| "No object") // No object
println("World" ||| "Hello")


println(1 &&& 10)   // 10
println(5.5 &&& 20) // 20
println(0 &&& 30)  // 0
println(0.0 &&& 40) // 0.0
println((null: String) &&& "Hello")  // null
println((null: AnyRef) &&& "No object") // null
println("World" &&& "Hello") // Hello
于 2013-02-12T00:31:05.180 に答える
2

モナドのようなものを考え出そうとしているようです。やりたいことは既に言語に組み込まれており、慣用的な scala では一般的です。私はモナドの専門家ではありませんが、オプションは一種のモナドだと彼らは言います。

あなたは具体的に書く能力を求めています:

val x = someString or "default string"

someString が false と評価される原因は何ですか? ほとんどの言語では、if( someString != null ) をテストしますが、それが例で行うことです。慣用的な scala は null の使用を避け、代わりに None を使用します。

したがって、scala 構文では、次のようになります。

val someString:Option[String] = getAString()

また

val someString:Option[String] = Some("whatever")

また

val someString:Option[String] = None

そして、次のようになります。

val x = someString getOrElse "default string"

これは、あなたが求めたものとほぼ同じです。

このようなものを自分で実装したい場合は、Option の getOrElse のインターフェースを見てください (同様のバージョンが Map や標準ライブラリの他の場所に存在します)。

final def getOrElse[B >: A](default: ⇒ B): B

この例では、Option someString は A で表される型 (つまり String) を持っています。B は A または A のスーパー型でなければなりません。戻り値の型は B (A の場合もあります) になります。例えば:

val x:Option[Int]=1
x getOrElse 1.0 // this will be an AnyVal, not Any.

AnyVal は、Int と Double の最も具体的な共通の祖先です。ここでは、Any ではなく AnyVal であることに注意してください。

AnyVal の代わりに Double にしたい場合は、x を Option[Double] にする必要があります (または別の暗黙的なものが必要です)。Int から Double への組み込みの暗黙的な変換がありますが、Option[Int] から Option[Double] への変換はありません。暗黙の変換は、ブール論理のためではなく、2 が Float に昇格する理由です。

あなたの演算子と暗黙のメソッドがこの種の問題に対する最良の解決策だとは思いません。Options、filter、exists、map、flatMap などを使用して、実行したい種類の操作を処理できる簡潔でエレガントな scala コードを記述する方法は多数あります。

これが役に立つかもしれません:

http://www.codecommit.com/blog/ruby/monads-are-not-metaphors

于 2013-02-12T04:17:06.030 に答える
1

バージョンのバージョンのみを作成しました|||。コンセプトを示すためです。コードは改善されるかもしれませんが、少し急ぎました。

// Create a type that does the conversion, C is the resulting type
trait Converter[A, B, C] {
  def convert(a: A, b: => B)(implicit bool: BooleanConverter[A]): C
}

trait LowerPriorityConverter {
  // We can convert any type as long as we know how to convert A to a Boolean
  // The resulting type should be a supertype of both A and B
  implicit def anyConverter[A <: C, B <: C, C] = new Converter[A, B, C] {
    def convert(a: A, b: => B)(implicit bool: BooleanConverter[A]) = if (a) a else b
  }

  // We can go more specific if we can find a view from B to A
  implicit def aViewConverter[B <% A, A] = anyConverter[A, A, A]
}

object Converter extends LowerPriorityConverter {

  // For Doubles, Floats and Ints we have a specialized conversion as long as the
  // second type is a Numeric

  implicit def doubleConverter[A <: Double: Numeric, B: Numeric] = 
    new Converter[A, B, Double] {
      def convert(a: A, b: => B)(implicit bool: BooleanConverter[A]) =
        if (a) a else implicitly[Numeric[B]].toDouble(b)
    }
  implicit def floatConverter[A <: Float: Numeric, B: Numeric] = 
    new Converter[A, B, Float] {
      def convert(a: A, b: => B)(implicit bool: BooleanConverter[A]) = 
        if (a) a else implicitly[Numeric[B]].toFloat(b)
    }
  implicit def intConverter[A <: Int: Numeric, B: Numeric] = 
    new Converter[A, B, Int] {
      def convert(a: A, b: => B)(implicit bool: BooleanConverter[A]) = 
        if (a) a else implicitly[Numeric[B]].toInt(b)
    }
}

// We have created a typeclass for the boolean converters as well, 
// this allows us to use more generic types for the converters
trait BooleanConverter[A] extends (A => Boolean)

trait LowerPriorityBooleanConverter {
  implicit def any2bool = new BooleanConverter[AnyRef] {
    def apply(s: AnyRef) = s != null
  }
}

object BooleanConverter extends LowerPriorityBooleanConverter {

  implicit def num2bool[T: Numeric] = new BooleanConverter[T] {
    def apply(n: T) = implicitly[Numeric[T]].zero != n
  }

  // Note that this could catch String as well
  implicit def seq2bool[T <% GenTraversableOnce[_]] = new BooleanConverter[T] {
    def apply(s: T) = s != null && !s.isEmpty
  }

}

// This is similar to the original post
implicit class NonBooleanLogic[A](x: A) {

  // Note that we let the implicit converter determine the return type 
  // of the method
  def |||[B, C](y: => B)(
    // make sure we have implicits for both a converter and a boolean converter
    implicit converter: Converter[A, B, C], bool: BooleanConverter[A]): C =
    // do the actual conversion
    converter.convert(x, y)
}

いくつかのテストの結果:

1 ||| 2                                       //> res0: Int = 1
(null: String) ||| "test"                     //> res1: String = test
1.0 ||| 2                                     //> res2: Double = 1.0
1 ||| 2.0                                     //> res3: Int = 1
List() ||| Seq("test")                        //> res4: Seq[String] = List(test)
1f ||| 2.0                                    //> res5: Float = 1.0
1f ||| 2f                                     //> res6: Float = 1.0
0f ||| 2.0                                    //> res7: Float = 2.0
0 ||| 2f                                      //> res8: Int = 2
2.0 ||| 2f                                    //> res9: Double = 2.0
2.0 ||| 3.0                                   //> res10: Double = 2.0
Seq("test") ||| List()                        //> res11: Seq[String] = List(test)
"" ||| "test"                                 //> res12: String = test

ご覧のとおり、型を保持するには、特定のパターンを使用する必要があります。ここで私自身の質問の 1 つに対する答えからそれを学びました:戻り値の型が引数の型と Scala の型パラメーターに基づいているメソッドを定義する方法は?

このアプローチの利点は、元のコードを変更せずに、特定の型に特定のコンバーターを追加できることです。

于 2013-02-12T21:17:26.073 に答える