Implementing ifTrue, ifFalse, ifSome, ifNone, etc. in Scala to avoid if(…) and simple pattern matching

前端 未结 3 610
夕颜
夕颜 2020-12-08 17:44

In Scala, I have progressively lost my Java/C habit of thinking in a control-flow oriented way, and got used to go ahead and get the object I\'m interested in first, and the

3条回答
  •  既然无缘
    2020-12-08 18:11

    First: we probably cannot reuse else, as it is a keyword, and using the backticks to force it to be seen as an identifier is rather ugly, so I'll use otherwise instead.

    Here's an implementation attempt. First, use the pimp-my-library pattern to add ifTrue and ifFalse to Boolean. They are parametrized on the return type R and accept a single by-name parameter, which should be evaluated if the specified condition is realized. But in doing so, we must allow for an otherwise call. So we return a new object called Otherwise0 (why 0 is explained later), which stores a possible intermediate result as a Option[R]. It is defined if the current condition (ifTrue or ifFalse) is realized, and is empty otherwise.

    class BooleanWrapper(b: Boolean) {
      def ifTrue[R](f: => R) = new Otherwise0[R](if (b) Some(f) else None)
      def ifFalse[R](f: => R) = new Otherwise0[R](if (b) None else Some(f))
    }
    implicit def extendBoolean(b: Boolean): BooleanWrapper = new BooleanWrapper(b)
    

    For now, this works and lets me write

    someTest ifTrue {
      println("OK")
    }
    

    But, without the following otherwise clause, it cannot return a value of type R, of course. So here's the definition of Otherwise0:

    class Otherwise0[R](intermediateResult: Option[R]) {
      def otherwise[S >: R](f: => S) = intermediateResult.getOrElse(f)
      def apply[S >: R](f: => S) = otherwise(f)
    }
    

    It evaluates its passed named argument if and only if the intermediate result it got from the preceding ifTrue or ifFalse is undefined, which is exactly what is wanted. The type parametrization [S >: R] has the effect that S is inferred to be the most specific common supertype of the actual type of the named parameters, such that for instance, r in this snippet has an inferred type Fruit:

    class Fruit
    class Apple extends Fruit
    class Orange extends Fruit
    
    val r = someTest ifTrue {
      new Apple
    } otherwise {
      new Orange
    }
    

    The apply() alias even allows you to skip the otherwise method name altogether for short chunks of code:

    someTest.ifTrue(10).otherwise(3)
    // equivalently:
    someTest.ifTrue(10)(3)
    

    Finally, here's the corresponding pimp for Option:

    class OptionExt[A](option: Option[A]) {
      def ifNone[R](f: => R) = new Otherwise1(option match {
        case None => Some(f)
        case Some(_) => None
      }, option.get)
      def ifSome[R](f: A => R) = new Otherwise0(option match {
        case Some(value) => Some(f(value))
        case None => None
      })
    }
    
    implicit def extendOption[A](opt: Option[A]): OptionExt[A] = new OptionExt[A](opt)
    
    class Otherwise1[R, A1](intermediateResult: Option[R], arg1: => A1) {
      def otherwise[S >: R](f: A1 => S) = intermediateResult.getOrElse(f(arg1))
      def apply[S >: R](f: A1 => S) = otherwise(f)
    }
    

    Note that we now also need Otherwise1 so that we can conveniently passed the unwrapped value not only to the ifSome function argument, but also to the function argument of an otherwise following an ifNone.

提交回复
热议问题