scala的implicit和magnet模式
问题导读:1、什么是scala?
2、什么是magnet模式?
3、ExecutionContext的获取方法有?
static/image/hrline/4.gif
对初学scala的人,implicit像一个黑魔法,来无影去无踪,像它的名字一样非常“含蓄”。
从某种意义上讲,implicit是一个类型系统的游戏。scala是强类型系统,所有的参数都需要符合类型预期,如果需要一个Int类型,你传来一个String,编译器会报类型不符错误。implicit的引入,使在报错之前还有一次机会,即:如果编译器在当前作用域内找到一个从String转换到Int的implicit定义函数,编译器会用这个implicit把你传给它的String转换成它需要的Int,于是一切又愉快的发生下去了。
当然这只是implicit的一种使用场景,spray.io(已合并到akka-http)的magnet模式利用的就是这个特性。
magnet模式magnet模式简单讲就是通过定义一个magnet类型作为统一的参数,然后针对需要重载的参数列表,类型等,在magnet类型的companion object中实现相应的转换为magnet类型的implitcit函数。
如:可以定义一个Magnet类型实现一个接受任意参数的add函数
def add(magnet: MyMagnet): magnet.Result = magnet()
sealed trait MyMagnet {
type Result
def apply(): Result
}
object MyMagnet {
//一个整形参数到MyMagenet的转换
implicit def fromInt(i: Int) =
new MyMagnet {
type Result = Int
def apply(): Result = i + 1
}
//一个String参数到MyMagenet的转换
implicit def fromString(s: String) =
new MyMagnet {
type Result = String
def apply(): Result = "hello " + s
}
//一个String参数加一个整形参数到MyMagenet的转换
implicit def fromStringAndInt(tuple: (String, Int)) =
new MyMagnet {
type Result = String
def apply(): Result = tuple._1 + tuple._2.toString
}
}
调用的时候可以:
scala> add(1)
res9: Int = 2
scala> add("world")
res10: String = hello world
scala> add("happy string ", 5)
res11: String = happy string 5
看到这里,你可能会说这不就是重载吗?java和scala原生就支持重载,但jvm对泛型(generics)的支持是通过类型擦除(type erasure)实现的,这意味着java常规的重载无法带类型参数,如:jvm无法区分下面这种类型不同的List参数。
scala> :paste
// Entering paste mode (ctrl-D to finish)
def add(a: List): Unit = {}
def add(a: List): Unit = {}
// Exiting paste mode, now interpreting.
<console>:8: error: double definition:
def add(a: List): Unit at line 7 and
def add(a: List): Unit at line 8
have same type after erasure: (a: List)Unit
def add(a: List): Unit = {}
而magnet模式正好可以弥补这个缺憾,另外magnet模式相当于把重载的实现从语言层面拉到了自己的代码逻辑中,有利于针对性的引入一些新技巧减少冗余代码。当然,magnet的缺点也是很明显的:额外的一层增加了代码的复杂度。
隐含参数implicit另外一个常用的场景是: 替代全局变量,作为某个执行上下文中的隐含参数。
如:scala中异步的一个重要方法是使用Future。Futrure语义清晰,使用优雅,比手动起线程不知道高到哪里去了;),但Future在后台其实还是通过线程来执行的,要用Future就需要一个指定的执行上下文环境(ExecutionContext ,一般是线程池)来跑Future。Future又是一个object(单例对象,不是普通类)没有地方放这个线程池的引用,解决方案只能是在所有Future的方法中加上ExecutionContext参数,方法很函数式,但接口略显冗余。好在scala有implicit,只要你调用Future时,上下文中有一个implicit的ExecutionContext变量,Future会自动在这个EC上跑代码。
所以scala的Future方法都有一个(implicit executor: ExecutionContext)参数
def onComplete(U ⇒ U)(implicit executor: ExecutionContext): Unit
不同于全局变量,你在调用Future方法时,想使用某个指定的ExecutionContext,还是可以把它作为参数显示的传递给Future方法,这个显示传递的参数会覆盖implicit的参数。
另:ExecutionContext的获取方法有
[*]直接引用全局EC。import scala.concurrent.ExecutionContext.Implicits.global
[*]akka的actor中,引用当前actor系统的EC。import context.dispatcher
[*]也可以手动创建一个独占使用,确保线程池里的线程不会被其他不相干任务耗尽。
import java.util.concurrent.Executors
import concurrent.ExecutionContext
//创建一个4个线程的线程池
val executorService = Executors.newFixedThreadPool(4)
implicit val ec = ExecutionContext.fromExecutorService(executorService)
资料来源:http://xun.im/2015/04/21/scala-implicit-and-magnet-pattern/
页:
[1]