问题
我们写代码是喜欢从左到右一路写过去的,多流畅,多顺手啊!
考虑这种代码:返回某些数据的json形式。
大概会这么写:
def getyou() = {
Json(Map("a"->1, "b"->2))
}
我写出了获取数据的表达式Map("a"->1, "b"->2)
,想起来要返回json,于是移动到行首,写上Json(
,再移动到行尾,写上)
。
不能从左到右一路写过去,不流畅!
假如数据对象有toJson方法,可以直接从左到右写data.toJson
,流畅!但是一般的对象没有toJson方法怎么办?
简单管道
我在昨夜想到了UNIX shell的管道: a | b | c
。如果Scala内置这种语法多好!
其实,利用Scala的DSL能力,也可以自行实现。看代码:
object Json {
def |:(obj: Map[_, _]) = {
val conc = obj.map{p => " " + p._1 + ": " + p._2}.mkString(",\n")
"{\n" + conc + "\n}"
}
}
Map("a"->1, "b"->2) |: Json
自定义了一个|:操作符。Scala允许用符号作为方法名,用起来像操作符,冒号后缀表示该方法的this参数在右侧。
实际上被编译器翻译成Json.|:(Map("a"->1, "b"->2))
于是就能从左到右流畅书写代码了
可惜的是不能轻松地连接多个管道,不能data |: Json |: Println,因为这种操作符是右结合的,调用顺序是从右到左,而不是从左到右。除非加上括号:
object Println {
def |:(obj: Any) = println(obj)
}
(Map("a"->1, "b"->2) |: Json) |: Println
你就免不了要跑到行首加一个括号,就不流畅了。
连续管道
办法还是有的,要想办法运用正常的左结合操作符。如果我们能扩展任意的对象,就好了。那就给任意对象扩展一个管道操作符吧!
终极实现 看代码:
implicit def pipify[T](t: T) = new {
def |[R](f: T=>R): R = f(t)
}
def json(obj: Map[_, _]) = obj |: Json
Map("a"->1, "b"->2) | json | println
实际上被编译器翻译成pipify(pipify(Map("a"->1, "b"->2)).|(json)).|(println)
哈哈,晕了没有?
完整示例
完整代码提供在这里,各位可以拿去用REPL或IDE的Scala WorkSheet运行一下。
val data = Map("a"->1, "b"->2)
object Json {
def |:(obj: Map[_, _]) = {
val conc = obj.map{p => " " + p._1 + ": " + p._2}.mkString(",\n")
"{\n" + conc + "\n}"
}
}
def json(obj: Map[_, _]) = obj |: Json
object Println {
def |:(obj: Any) = println(obj)
}
implicit def piping[T](t: T) = new {
def |[R](f: T=>R): R = f(t)
}
(data |: Json) |: Println
data | json | println
(Update: 发现有写|>
的流派,总之用者自选)
Ruby之类的动态语言也能类似地扩展,但可能做不到这么简洁。大概能写成:
#a为任意类型的对象
#简单管道
a.pipe b
#连续管道
a.pipe(b).pipe(c)
如果谁能用Ruby实现得更简洁,还请告诉我!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。