2009-01-27 71 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!