Scala中非布尔类型的逻辑运算符

Scala中非布尔类型的逻辑运算符,scala,boolean,type-safety,Scala,Boolean,Type Safety,我喜欢使用布尔运算符编写的简洁代码,而不是使用Lisp、Python或JavaScript等(通常是动态的)语言编写的条件语句,如典型的: x = someString or "default string" vs 在Scala中,我想到的是: object Helpers { case class NonBooleanLogic[A](x: A) { // I could overload the default && and || // but I th

我喜欢使用布尔运算符编写的简洁代码,而不是使用Lisp、Python或JavaScript等(通常是动态的)语言编写的条件语句,如典型的:

x = someString or "default string"
vs

在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
    类型
  • 这太酷了,其他人以前肯定做过,可能是在一个更好的文档化、测试和全面的库中。在哪里可以找到它

  • 我将坚持选择[T]。。。这对Scala来说更为惯用。我还经常在验证中使用它,例如:在web表单中,有时不应将空字符串视为有效的用户输入

    例如,如果您认为空字符串/长度为零的字符串(
    )为false,任何空引用也为false,任何数字零为false,则可以编写以下隐式def

    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
    

    听起来你好像在想一个单子。您想要做的事情已经内置到该语言中,并且在惯用的scala中很常见。我不是单子方面的专家,但他们说一种选择是单子的一种

    你特别要求具备写作能力:

    val x = someString or "default string"
    
    什么使someString的计算结果为false?在大多数语言中,您都会测试if(someString!=null),这就是您在示例中所做的。惯用的scala避免使用null,而是使用None

    因此,在scala语法中

    val someString:Option[String] = getAString()
    

    然后你会:

    val x = someString getOrElse "default string"
    
    这几乎正是你想要的

    如果您想自己实现类似的功能,请查看getOrElse in选项的接口(Map和标准库中的其他地方也有类似的版本):

    在本例中,选项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作为选项[Double](或者需要另一个隐式)。有一个内置的从Int到Double的隐式转换,但不是从Option[Int]到Option[Double]。隐式转换是2被提升为浮点的原因,而不是因为布尔逻辑

    我不认为你的运算符及其方法是解决这类问题的最佳方法。有很多方法可以使用选项、过滤器、exists、map、flatMap等编写简洁优雅的scala代码,它们可以处理您想要执行的各种操作

    您可能会发现这很有帮助:


    我只为
    | | |
    版本创建了一个版本。这是为了展示这个概念。代码可能会改进,我有点匆忙

    // 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)
    }
    
    如您所见,为了保留类型,我们需要使用特定的模式。我在这里回答了自己的一个问题:


    这种方法的优点是,您可以为特定类型添加特定的转换器,而无需更改原始代码。

    顺便说一句,这更为惯用:
    val x=if(someString!=null&&someString.size()>0)someString其他“默认字符串”
    val x=Option(someString).filterNot(u.isEmpty).getOrElse(“默认字符串”)或
    val x=if(someString!=null&&someString.size()>0)someString其他“默认字符串”
    我想我会将代码片段改为Python,以避免反复回答相同的问题。另请参见:-您展示的代码示例中没有一个真正依赖选项是monad。单子就像一个接口;它描述了一种类型能做的一些,但不一定全部。是的。。。我回答了他问题的一个具体部分。他更一般的想法是,将某些值转换为奇怪的
    false
    ,然后将值映射到值。。。似乎有关联。不过,我可能错认为存在任何关联。好吧,只要坚持使用
    选项
    类型和
    getOrElse
    就会使
    方法有点孤立。。。在创建或重写逻辑运算符时引入它似乎有点多余,甚至是不必要的:-/尽管我喜欢隐式的
    Numeric
    转换D
    val x = someString getOrElse "default string"
    
    final def getOrElse[B >: A](default: ⇒ B): B
    
    val x:Option[Int]=1
    x getOrElse 1.0 // this will be an AnyVal, not Any.
    
    // 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