2009-01-27 79 views
24

直角三角形的斜邊的正方形等於另外兩邊的正方形的總和。如何在斯卡拉書寫畢達哥拉斯定理?

這是畢達哥拉斯定理。根據邊的長度「a」和「b」計算斜邊的函數將返回sqrt(a * a + b * b)。

問題是,你會如何在Scala中定義這樣一個函數,以便它可以用於任何實現適當方法的類型?

對於上下文,想象一下您希望與Int,Double,Int-Rational,Double-Rational,BigInt或BigInt-Rational類型一起使用的數學定理庫,具體取決於您在做什麼以及速度,精度,精度和範圍要求。

+2

現在我終於知道爲什麼結構類型不會讓我這樣做:http://article.gmane.org/gmane.comp.lang.scala/7013 – 2010-12-02 12:02:20

回答

24

這僅適用於斯卡拉2.8,但它的工作:

scala> def pythagoras[T](a: T, b: T, sqrt: T => T)(implicit n: Numeric[T]) = { 
    | import n.mkNumericOps 
    | sqrt(a*a + b*b) 
    | } 
pythagoras: [T](a: T,b: T,sqrt: (T) => T)(implicit n: Numeric[T])T 

scala> def intSqrt(n: Int) = Math.sqrt(n).toInt 
intSqrt: (n: Int)Int 

scala> pythagoras(3,4, intSqrt) 
res0: Int = 5 

更一般地說,性狀Numeric實際上是關於如何解決這類問題的參考。另見Ordering

18

最明顯的方法:

type Num = { 
    def +(a: Num): Num 
    def *(a: Num): Num 
} 

def pyth[A <: Num](a: A, b: A)(sqrt: A=>A) = sqrt(a * a + b * b) 

// usage 
pyth(3, 4)(Math.sqrt) 

這是可怕的原因有很多。首先,我們有遞歸類型的問題,Num。只有在您將-Xrecursive選項設置爲某個整數值(5對數字來說可能綽綽有餘)的情況下編譯此代碼時,才允許這樣做。其次,類型Num是結構化的,這意味着它定義的成員的任何用法都將被編譯成相應的反射調用。溫和地說,這個版本的pyth是低效率的,運行速度比常規實現低幾十萬分之一秒。雖然如果您想要定義pyth任何類型,它定義了+,*,並且存在sqrt函數,但無法繞過結構類型。

最後,我們談到最根本的問題:它太複雜了。爲什麼要用這種方式來實現這個功能呢?實際上,它唯一需要應用的類型是真正的Scala數字。因此,最簡單的做法如下:

def pyth(a: Double, b: Double) = Math.sqrt(a * a + b * b) 

所有問題都解決了!由於隱式轉換的奇蹟,此函數可用於Double,Int,Float類型的值,即使是奇怪的類型如Short。雖然這種功能的確在技術上比我們的結構型版本更不靈活,但它更加高效且顯着更具可讀性。我們可能已經失去了計算定義爲+*的不可預見類型的Pythagrean定理的能力,但我認爲你不會錯過這個能力。

+2

「簡單」解決方案BigNum還是Rational?我可以定義一個完整的數學定理庫,並讓它們使用double,integer,bignum或rational嗎? – 2009-01-29 14:33:23

+1

+1既適用於實施,也適用於推理,爲什麼不應該這樣做。 :-) – 2009-02-04 11:23:33

+1

現在我對Scala有了更多的瞭解,我發現存在另一個解決方案。定義一個抽象Num類,任何所需類型的子類,從所需類型到相應子類的隱式轉換,並使pyth [A]接受A的「a」和「b」,再加上隱含的A => Num [A ]。你介意在解決方案中加入這個解決方案嗎?我想接受它,但我更喜歡它更完整。 – 2009-07-06 21:21:51

0

java.lang中有一個方法。數學:

public static double hypot (double x, double y) 

爲其的javadoc斷言:

返回SQRT(X 2 + Y),沒有中間溢出或下溢。

尋找到src.zip,Math.hypot使用StrictMath,這是一個本地方法:

我已經experimented一概而論NumericReal

public static native double hypot(double x, double y); 
2

對丹尼爾的回答的幾點思考,這個功能更適合提供sqrt功能。這將導致:

def pythagoras[T](a: T, b: T)(implicit n: Real[T]) = { 
    import n.mkNumericOps 
    (a*a + b*b).sqrt 
} 

這是棘手的,但可能,在這樣的通用函數中使用文字數字。如果sqrt在第二個參數列表傳遞

def pythagoras[T](a: T, b: T)(sqrt: (T => T))(implicit n: Numeric[T]) = { 
    import n.mkNumericOps 
    implicit val fromInt = n.fromInt _ 

    //1 * sqrt(a*a + b*b) Not Possible! 
    sqrt(a*a + b*b) * 1 // Possible 
} 

類型推斷效果更好。

參數ab將作爲對象傳遞,但@specialized可以解決此問題。不幸的是,數學運算中仍然會有一些開銷。

你可以差不多沒有導入mkNumericOps。我得到了frustratringly close!