我喜欢使用布尔运算符编写的简洁代码,而不是像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
}
但是出现了两个问题:
Any
类型的类型?答案 0 :(得分:3)
我将坚持使用Option [T] ...这对Scala更为惯用。我也经常在验证中使用它,例如:在Web表单中,有时不应将空字符串视为有效的用户输入。
例如,如果您认为长度为零(""
)的null String / String为false,则任何空引用也为false,并且任何数字零都为false,您可以编写以下隐式defs。 / p>
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
答案 1 :(得分:2)
听起来你正试图想出像Monad这样的东西。你想要做的事情已经内置在语言中,并且在惯用语中很常见。我不是Monads的专家,但是他们说Option是一种Monad。
你特别要求写作的能力:
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表示的类型(即字符串).B必须是A或A的超类型。返回类型将是B(可能是A)。例如:
val x:Option[Int]=1
x getOrElse 1.0 // this will be an AnyVal, not Any.
AnyVal是Int和Double的最具体的共同祖先。请注意,这是AnyVal,而不是Any。
如果你想要它是Double而不是AnyVal,你需要x作为Option [Double](或者你需要另一个隐含的)。有一个从Int到Double的内置隐式转换,但不是从Option [Int]到Option [Double]。隐式转换是为什么你的2被提升为Float的原因,而不是因为你的布尔逻辑。
我不认为你的操作符和implicits方法是解决这类问题的最佳方法。使用Options,filter,exists,map,flatMap等编写简洁优雅的scala代码有很多方法可以处理你想要执行的各种操作。
您可能会觉得这很有用:
http://www.codecommit.com/blog/ruby/monads-are-not-metaphors
答案 2 :(得分: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
如您所见,为了保留类型,我们需要使用特定模式。我从答案中回答了我自己的一个问题:How to define a method for which the returntype is based on types of argument and type parameter in Scala?
这种方法的优点是您可以为特定类型添加特定转换器,而无需更改原始代码。