2013-02-11 80 views
4

我喜歡可使用布爾運算符,而不是條件語句在如Lisp,Python或JavaScript的(通常是動態的)語言編寫的簡明代碼,如在典型的:邏輯運算用於非布爾類型Scala中

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 
} 

但有兩個concer ns出現:

  1. 如何使它適用於具有共同祖先的類型,而不會恢復爲Any類型?
  2. 這是太酷了別人必須做之前,大概在一個更好的說明,測試和綜合庫。哪裏可以找到它?
+0

順便說一句,這是更習慣︰'val x = if(someString!= null && someString.size()> 0)someString else「default string」;' – 2013-02-11 22:46:43

+0

它的意思是JavaScript,而不是Scala – fortran 2013-02-11 22:48:21

+0

你的第二個片段可以寫成'val x = Option(someString).filterNot(_。isEmpty).getOrElse(「default string」)'或'val x = if(someString!= null && someString.size()> 0)someString else 「default string」 – 2013-02-11 22:49:20

回答

3

我只會堅持Option [T] ...這對Scala來說更具慣用。我還經常用它的驗證,例如:Web表單,有時一個空字符串不應該被視爲一個有效的用戶輸入。例如,如果您認爲零長度的空字符串/字符串("")爲false,則任何空引用也爲false,並且任何數字零爲false,則可以編寫以下隱式defs。

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") 

如果你真的需要定義你自己的操作,將其轉換爲持有選項[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 
+0

好吧,只要堅持'Option'類型並且'getOrElse'稍微離開'and'方法孤立的......並在創建或覆蓋邏輯運算符時引入它似乎有點多餘或甚至是不必要的: - 儘管我喜歡隱式的「Numeric」轉換! :D – fortran 2013-02-12 10:08:29

2

這聽起來像你想要拿出像Monad這樣的東西。你想要做的是已經內置到語言中,並且在慣用的scala中很常見。我不是Monad的專家,但他們說Option是一種Monad。

你專門要求寫的能力:

val x = someString or "default string" 

是什麼讓someString評估爲假的?在大多數語言中,你會測試if(someString!= null),這就是你在你的例子中所做的。習慣scala避免使用null,而是使用None。

所以,在斯卡拉語法,你必須

val someString:Option[String] = getAString() 

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

val someString:Option[String] = None 

,然後你必須:

val x = someString getOrElse "default string" 

這幾乎正是你問什麼。

如果你想實現你自己這樣的事情,看看在getOrElse在選項界面(類似版本的地圖等地存在於標準庫):

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的最具體的共同祖先。請注意,這裏是AnyVal,而不是Any。

如果你希望它是Double而不是AnyVal,你需要x是一個Option [Double](或者你需要另一個隱式的)。有從Int到Double的內置隱式轉換,但不是從Option [Int]到Option [Double]。隱式轉換是爲什麼你的2被升級到一個浮點數,而不是你的布爾邏輯。

我不認爲你的操作符和implicits方法是解決這類問題的最佳方法。有許多方法可以使用Options,filter,exists,map,flatMap等來編寫簡潔優雅的scala代碼,它們可以處理您想要執行的操作。

你可能會有所幫助:

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

+0

另請參閱:http://james-iry.blogspot.co.uk/2007/09/monads-are-elephants-part-1.html?m=1 - 您顯示的任何代碼示例實際上都依賴於選項是一個monad。 monad就像一個接口;它描述了某種類型可以做的一些但不一定全部。 – 2013-02-12 08:59:23

+0

是啊......我正在回答他的問題的一個特定部分。他的更一般的想法,將某些價值轉化爲怪異的「假」概念,然後將價值映射到價值......似乎相關。儘管我有可能存在任何相關性,但我可能會錯。 – Brian 2013-02-13 12:57:15

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?

這種方法的優點是可以爲特定類型添加特定的轉換器而不會改變原始代碼。