scala的類型參數是否/能否具有C++ template一樣的表現能力,是否有其他語言具有類似能力?

C++的模板功能強大,能提供自定義類型和內置類型一致的語法和性能,但用C++寫程序太費神,各種類庫又喜歡造各種基礎設施的輪子,包括各種容器。同時C++的類編譯完後會在內存中散布得到處都是,在二進位層面上已經完全失去了類的結構,又沒有統一的ABI標準,所以動態的類型信息很少。

而另一方面,Java運行在虛擬機中,各種內省信息都保留完整,而且內存保護得很好,不會象C++那樣因為內存管理不善出問題。 scala是一個可能的選項,其抽象能力很強,但根據我這幾天的研究發現,scala的泛型遠不如C++靈活,限制很多,比如寫一個泛型的abs c++可以寫

template T abs(T x) {return x&>=0?x:-x;}

按照同樣的思路寫scala就會出錯:

def abs[T](x:T)=if x&>=0 then x else -x

即使加上類型約束:Numeric也不行,Numeric[T]是無法和其他基本數值類型一起運算的,硬要寫也行,但效率會因為box unbox下降很多。而且更令人髮指的是Numeric有fromInt卻沒有fromDouble 綜上泛型程序依然很難寫 要實現象scala那樣的抽象能力,同時又對泛型有很好的支持 有沒有更好的解決方案呢?


注意: 本答案的例子僅用作展示 Scala 中 typeclass 的用法. 事實上, 例子中 abs 函數並不是一個合適的對 "絕對值" 的抽象. 一種在數學上更為合理的抽象方法應該是: 定義一個 trait 叫 MetricSpace, 然後叫實現者提供 distance 方法.

想要在 Scala 里自如地使用泛型數學運算, 就要先造一堆必要的抽象代數結構. 下面我們來考察一下題主這個 abs 函數. 這個函數要求它的泛型參數 T 具備:

  1. 逆元
  2. 偏序關係

為了以後方便, 乾脆我們直接定義一個 T 的加群和這個群中元素間的偏序關係好了. 下面開始定義加群和偏序, 即定義兩個 trait:

trait AdditiveGroup[T] {
def zero: T
def neg(x: T): T
def add(x: T, y: T)
}

trait Ordering[T] {
def ?(x: T, y: T): Boolean
def ?(x: T, y: T) = ?(y, x)
}

然後, 你的 abs 函數就可以這麼寫

def abs[T](v: T)(implicit g: AdditiveGroup[T], o: Ordering[T]) = {
if (o.?(v, g.zero)) v else g.inv(v)
}

這個函數在被調用的時候, 用戶不需要手動傳入 g 和 o 這兩個參數. Scala 編譯器會從當前作用域中搜索類型匹配的 g 和 o. 如果沒找到則報編譯錯. 注意 o.?(v, g.zero) 和 g.inv(v) 這兩個寫法非常醜陋不易讀. 為了提高易讀性, 我們可以把求逆序關係操作寫成符號, "塞" 到所有的類裡面去:

implicit class Sweetener[T](val x: T) extends AnyVal {
def +(that: T)(implicit g: AdditiveGroup[T]) = g.add(x, that)
def unary_-(implicit g: AdditiveGroup[T]) = g.neg(x)
def ?(that: T)(implicit o: Ordering[T]) = o.?(x, that)
}

這樣, 在 import 這個 Sweetener 類之後, abs 函數就可以修改為:

def abs[T](v: T)(implicit g: AdditiveGroup[T], o: Ordering[T]) = {
if (v ? g.zero) v else -v
}

看, 函數體和題主給的長得幾乎一樣了. 當然題主要是沒有代碼易讀性方面的強迫症, 這一步不是必要的. 我把 + 也塞到了 T 中, 是為了以後可以避免寫 g.add(x, y), 直接寫 x + y.

還沒完, 這個 abs 函數現在還不能工作, 因為我們沒有提供默認的加群和偏序關係. 提供的方法是實現之前定義的兩個 trait. 作為例子, 這裡為 Int 和 Double 提供默認實現:

object Implicits {
implicit object IntAdditiveGroup extends AdditiveGroup[Int] {
override def zero: Int = 0
override def neg(x: Int): Int = -x
override def add(x: Int, y: Int): Int = x + y
}

implicit object IntOrdering extends Ordering[Int] {
override def ?(x: Int, y: Int): Boolean = x &<= y } implicit object DoubleAdditiveGroup extends AdditiveGroup[Double] { override def zero: Double = 0.0 override def neg(x: Double): Double = -x override def add(x: Double, y: Double): Double = x + y } implicit object DoubleOrdering extends Ordering[Double] { override def ?(x: Double, y: Double): Boolean = x &<= y } implicit class Sweetener[T](val x: T) extends AnyVal { def +(that: T)(implicit g: AdditiveGroup[T]) = g.add(x, that) def ?(that: T)(implicit o: Ordering[T]) = o.?(x, that) } }

Scala 要求 implicit 的東西不能寫在 package 這一層. 所以我把這些 implicit 實現都包進了名為 Implicits 的一個 object. 注意我把 Sweetener 也粘進去了, 方便用的時候一行 import 全部導入.

到此, 題主所求的功能就實現好了. 我們來寫一個 main 函數試試:

import Implicits._
object Test {
def main(args: Array[String]) {
val intTest = abs(-4) // 得到 4
val doubleTest = abs(-4.5) // 得到 4.5
}
}

另外, 可以看到, 經過這一番精心抽象和封裝還之後, 再加上 Scala 的類型推斷功能, abs 函數在使用時非常簡單. 希望這個例子能展現 Scala 的表達能力.


實現 Scala 下泛型的數學編程應該使用 typeclass。效率問題交由宏 (macro) 解決。

實現這一功能的庫有 Spire (non/spire · GitHub)。它完全實現了你的需求,可以做到基本沒有效率損失。

import spire.algebra._

def abs[X](x: X)(implicit ev: Signed[X]): X = ev.abs(x)

如果覺得 Scala 的 typeclass 比較繁瑣,可以考慮 Haskell。


Typed Racket 吧


看樓上Typeclass已經被寫爛了哇!非Typeclass版本來一發,Magnet大法好啊

def abs(magnet: MyMagnet): magnet.Result = magnet()
sealed trait MyMagnet {
type Result
def apply(): Result
}
object MyMagnet {
implicit def fromInt(i: Int) = new MyMagnet {
type Result = Int
def apply(): Result = if (i &>= 0) i else -i
}
implicit def fromLong(i: Long) = new MyMagnet {
type Result = Long
def apply(): Result = if (i &>= 0) i else -i
}
implicit def fromFloat(i: Float) = new MyMagnet {
type Result = Float
def apply(): Result = if (i &>= 0) i else -i
}
implicit def fromDouble(i: Double) = new MyMagnet {
type Result = Double
def apply(): Result = if (i &>= 0) i else -i
}
implicit def fromBigInt(i: BigInt) = new MyMagnet {
type Result = BigInt
def apply(): Result = if (i &>= 0) i else -i
}
implicit def fromSomeInt(s: Option[Int]) = new MyMagnet {
type Result = Option[Int]
def apply(): Result = s match {
case Some(i) =&> Some(if (i &>= 0) i else -i)
case _ =&> None
}
}
implicit def fromListInt(l: List[Int]) = new MyMagnet {
type Result = List[Int]
def apply(): Result = l.map(i =&> if(i &>= 0) i else -i)
}
//...
}
println(abs(-1))
println(abs(-1L))
println(abs(-1.0))
println(abs(-1.0D))
println(abs(BigInt(-10000000)))
println(abs(Some(-100)))
println(abs(List(-1, 2, -3)))


很顯然你需要Rust


樓上幾位說的type class在標準庫中就有,直接用吧

scala&> import scala.math.Numeric
import scala.math.Numeric

scala&> import scala.math.Ordering.Implicits._
import scala.math.Ordering.Implicits._

scala&> import scala.math.Numeric.Implicits._
import scala.math.Numeric.Implicits._

scala&> def abs[T: Numeric](x:T) = if (x &>= implicitly[Numeric[T]].zero) x else -x
abs: [T](x: T)(implicit evidence$1: scala.math.Numeric[T])T


你說的不就是rust

Abstraction without overhead: traits in Rust


Type Class可以解決這個泛型問題,不過寫起來比較麻煩,比如,

trait NumericLike[T] {

def abs(x: T): T

}

object NumericLike {

implicit object NumericLikeDouble extends NumericLike[Double] {

def abs(x: Double) = if (x &>= 0) x else -x

}

implicit object NumericLikeInt extends NumericLike[Int] {

def abs(x: Int) = if (x &>= 0) x else -x

}

}

def abs[T](x: T)(implicit e: NumericLike[T]): T = e.abs(x)

def abs[T](x: T)(implicit e: Numeric[T]): T = e.abs(x)


推薦閱讀:

TAG:編程語言 | Scala | Java虛擬機JVM | C | 泛型Generic |