Scala的Pattern Matching Anonymous Functions

devos發表於2014-11-19

參考自http://stackoverflow.com/questions/19478244/how-does-a-case-anonymous-function-really-work-in-scala

http://www.scala-lang.org/files/archive/nightly/pdfs/ScalaReference.pdf

http://docs.scala-lang.org/overviews/core/futures.html

 

在第三篇文件《Futures and Promises》中,講到了Future物件有三個方法可以註冊callback

import scala.util.{Success, Failure}

val f: Future[List[String]] = future {
  session.getRecentPosts
}

f onComplete {
  case Success(posts) => for (post <- posts) println(post)
  case Failure(t) => println("An error has occured: " + t.getMessage)
}

f onFailure {
  case t => println("An error has occured: " + t.getMessage)
}

f onSuccess {
  case posts => for (post <- posts) println(post)
}

傳給onComplete、onFailture和onSuccess的都是

{ case p1 => b1 ... case pn => bn }

形式的語句,但是這三個方法接受的引數型別卻是不同的。

abstract def onComplete[U](f: (Try[T]) ⇒ U)(implicit executor: ExecutionContext): Unit

def onSuccess[U](pf: PartialFunction[T, U])(implicit executor: ExecutionContext): Unit

def onFailure[U](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Unit

onCompelete的引數型別的是一個 (Try[T]) => U函式, 而onSuccess和onFailure的引數型別是偏函式。

那麼,問題來了……{ case p1 => b1 ... case pn => bn } 的型別到底是啥呢?

在<The Scala Language Specification>的第8.5章給出了說明:

An anonymous function can be defined by a sequence of cases

{case p1 =>b1 ...case pn =>bn }

which appear as an expression without a prior match.
The expected type of such an expression must in part be defined. It must be either scala.Functionk[S1, ..., Sk, R] for some k >0, or scala.PartialFunction[S1, R], where the argument type(s) S1, ..., Sk must be fully determined, but the result type R may be undetermined. 

也就是說{ case p1 => b1 ... case pn => bn } 這種表示式的值的型別可以有兩種,要不是一個函式,要不是一個偏函式(偏函式也是一種函式)。在這個表示式的位置上需要哪種型別,編譯器就會用這個表示式生成對應的型別。但是無論是生成函式還是偏函式,它們的引數的型別都必須是確定的,對於一個特定的Future物件,onComplete接受的函式的引數型別是Try[T],而onSuccess接受的PartialFunction的引數型別是T,onFailure接受的PartialFunction的引數型別是Throwable。但是這些函式的返回型別U可以不是需要這個{ case p1 => b1 ... case pn => bn } 表示式的地方指定的。比如,這三個onXXX方法都沒有指定它所接受的函式的返回值型別。

 

例子:

import java.io.IOException

import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Success
import scala.util.Failure


object CallbacksOfFuture extends App {

  def getRecentPosts = {
    Thread.sleep(5000)
    "Good morning" :: "Good afternoon" :: Nil
    throw new TimeoutException("Goodbye")
  }

  val f: Future[List[String]] = future {
    val posts = getRecentPosts
    posts
  }
//onComplete A f onComplete {
case Success(posts) => posts.foreach(println) case Failure(e) => println("An error has occured: " + e.getMessage) }
//onComplete B f onComplete { result
=> result match { case Success(posts) => posts.foreach(println) case Failure(e) => println("An error has occured: " + e.getMessage) } } //won't compile // f onComplete{ // case 1 => 2 // } f onSuccess { case posts => posts foreach println } f onFailure{ case r: IOException => println("got IOException: " + r.getMessage) case r: TimeoutException => println("got TimeoutException: " + r.getMessage) case e => println("An error has occured: " + e.getMessage) } f flatMap{ posts => future{posts}} foreach(println) f map (posts => posts) foreach println Thread.sleep(10000) }

 那麼onComplete和onSuccess、onFailure還有啥不同呢?

如果我們把onComplete A實現裡的case Failure去掉,那麼在執行時就會報MatchError,因為

f onComplete {
    case Success(posts) => posts.foreach(println)
    case Failure(e) => println("An error has occured: " + e.getMessage)
  }

實際上會被翻譯為:

 f onComplete { result =>
    result match {
      case Success(posts) => posts.foreach(println)
      case Failure(e) => println("An error has occured: " + e.getMessage)
    }
  }

當result是一個Failure,那麼在去掉case Failure後,它會無法得到匹配,從而報出MatchError.

而在

  f onFailure{
    case r: IOException => println("got IOException: " + r.getMessage)
    case r: TimeoutException => println("got TimeoutException: " + r.getMessage)
    case e => println("An error has occured: " + e.getMessage)
  }

如果我們只留下case r: IOException,雖然執行時產生的異常是TimeoutException,但是執行時卻不會報錯。這是為啥呢?

看Future的原始碼吧

 def onFailure[U](callback: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Unit = onComplete {
    case Failure(t) =>
      callback.applyOrElse[Throwable, Any](t, Predef.conforms[Throwable]) // Exploiting the cached function to avoid MatchError
    case _ =>
  }

原來onFailure會將callback註冊給onComplete,這使得呼叫onFailure也不會阻塞。當Future的執行結果為Failure時,Failure中包裝的異常會被apply給t, 如果apply失敗,會執行Predef.confirm[Throwable]。這個函式是這樣的:

 sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
  private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x }
  // not in the <:< companion object because it is also
  // intended to subsume identity (which is no longer implicit)
  implicit def conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]

在這裡,confirm的型別引數為Throwable,於是 singleton_<:<.asInstanceOf[A <:< A]被型別為換為 <:<[Throwable <:< Throwable]。

singleton_<:<本身是一個物件,它的超類的型別是 Any => Any,因此,singleton_<:<.asInstanceOf[Throwable <:< Throwable]是一個型別為(Throwable) => Throwable的函式。因此conform在onFailure中的使用是型別正確的。

那麼onform返回的這個函式幹了啥呢,它的apply方法接收x,返回x。用於onFailure的環境中時,就相當於

def onFailure[U](callback: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Unit = onComplete {
    case Failure(t) =>
      callback.applyOrElse[Throwable, Any](t, (e) => e) // Exploiting the cached function to avoid MatchError
    case _ =>
  }

在callback.applyOrElse方法中,我們需要一個函式,它的型別是(Throwable) => Any,又沒有副作用。那麼用Predef.conform[Throwable]得到一個實際上啥都沒幹的(Throwable) => (Throwable)是很合適的。

應該說這麼寫挺規範吧……

相關文章