バージョンのバージョンのみを作成しました|||
。コンセプトを示すためです。コードは改善されるかもしれませんが、少し急ぎました。
// 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 の型パラメーターに基づいているメソッドを定義する方法は?
このアプローチの利点は、元のコードを変更せずに、特定の型に特定のコンバーターを追加できることです。