24

The square of the hypotenuse of a right triangle is equal to the sum of the squares on the other two sides.

This is Pythagoras's Theorem. A function to calculate the hypotenuse based on the length "a" and "b" of it's sides would return sqrt(a * a + b * b).

The question is, how would you define such a function in Scala in such a way that it could be used with any type implementing the appropriate methods?

For context, imagine a whole library of math theorems you want to use with Int, Double, Int-Rational, Double-Rational, BigInt or BigInt-Rational types depending on what you are doing, and the speed, precision, accuracy and range requirements.

Daniel C. Sobral
  • 284,820
  • 82
  • 479
  • 670

4 Answers4

24

This only works on Scala 2.8, but it does work:

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

More generally speaking, the trait Numeric is effectively a reference on how to solve this type of problem. See also Ordering.

Daniel C. Sobral
  • 284,820
  • 82
  • 479
  • 670
  • You could update this popular answer to use context bounds, now that those exist. – Brian McCutchon Nov 01 '17 at 06:57
  • @BrianMcCutchon I believe context bounds were in fact available on 2.8, but then I need to assign them to a variable anyway so that I can import `mkNumericOps` from it. There's a better solutions now, though -- though, if you want it, please ask in a separate question. – Daniel C. Sobral Jan 10 '18 at 19:48
  • 2
    Are you referring to `import Numeric.Implicits._`? That would allow you to use context bounds without an evidence variable. – Brian McCutchon Jan 10 '18 at 22:49
18

The most obvious way:

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)

This is horrible for many reasons. First, we have the problem of the recursive type, Num. This is only allowed if you compile this code with the -Xrecursive option set to some integer value (5 is probably more than sufficient for numbers). Second, the type Num is structural, which means that any usage of the members it defines will be compiled into corresponding reflective invocations. Putting it mildly, this version of pyth is obscenely inefficient, running on the order of several hundred thousand times slower than a conventional implementation. There's no way around the structural type though if you want to define pyth for any type which defines +, * and for which there exists a sqrt function.

Finally, we come to the most fundamental issue: it's over-complicated. Why bother implementing the function in this way? Practically speaking, the only types it will ever need to apply to are real Scala numbers. Thus, it's easiest just to do the following:

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

All problems solved! This function is usable on values of type Double, Int, Float, even odd ones like Short thanks to the marvels of implicit conversion. While it is true that this function is technically less flexible than our structurally-typed version, it is vastly more efficient and eminently more readable. We may have lost the ability to calculate the Pythagrean theorem for unforeseen types defining + and *, but I don't think you're going to miss that ability.

Daniel Spiewak
  • 52,267
  • 12
  • 104
  • 120
  • 2
    Does the "simple" solution work with BigNum or Rational? Can I define a whole library of math theorems and have them used by either double, integer, bignum or rational? – Daniel C. Sobral Jan 29 '09 at 14:33
  • 1
    +1 for both the implementation and the reasoning why it should not be done like this. :-) – Andrzej Doyle Feb 04 '09 at 11:23
  • 1
    Now that I'm much more informed about Scala, I see that another solutione exists. Defining an abstract Num class, subclasses for any desired type, implicit conversions from the desired types to the corresponding subclass, and making pyth[A] accept "a" and "b" of A, plus an implicit from A => Num[A]. Would you mind adding this solution to your answer? I'd like to accept it, but I'd prefer for it to be more complete. – Daniel C. Sobral Jul 06 '09 at 21:21
  • 1
    I has to be noted that this solution does not work for long values. Not all long values can be represented in a double. – Thomas Jung Jan 20 '10 at 06:46
2

Some thoughts on Daniel's answer:

I've experimented to generalize Numeric to Real, which would be more appropriate for this function to provide the sqrt function. This would result in:

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

It is tricky, but possible, to use literal numbers in such generic functions.

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
}

Type inference works better if the sqrt is passed in a second parameter list.

Parameters a and b would be passed as Objects, but @specialized could fix this. Unfortuantely there will still be some overhead in the math operations.

You can almost do without the import of mkNumericOps. I got frustratringly close!

retronym
  • 53,652
  • 11
  • 151
  • 168
  • One and zero ought to be enough numbers for anybody! :) – retronym Mar 05 '10 at 19:16
  • 4
    I would like *e*, *π* and *i* too, so I can express Euler's Identity. – Donal Fellows Jul 11 '10 at 16:39
  • Hey, I'm wondering why I have to do something like: `import n._; implicit val fromInt = n.fromInt _; x * x + 1` to get an expression like `x * x + 1` to compile. Why isn't it enough to just have `import n._`? Because that import includes n.fromInt, doesn't it? And shouldn't `n.fromInt` be implicitly called on the Int when I try to add it to a type T? – John Peter Thompson Garcés Aug 01 '16 at 15:34
  • @JohnPeterThompsonGarcés Because `n.fromInt` isn't defined as an implicit method in `Numeric`, so you have to do it yourself. Also, the implicit `fromInt` won't work for e.g. `1 + x`, as `1` would have to be converted first to `T`, then to `Ops`, and Scala doesn't let you chain conversions like that (for good reasons, I'm sure). You could define a type yourself that provides all these, though, including a version of `fromInt` that returns an `Ops`. – Brian McCutchon Nov 01 '17 at 07:22
0

There is a method in java.lang.Math:

public static double hypot (double x, double y)

for which the javadocs asserts:

Returns sqrt(x2 +y2) without intermediate overflow or underflow.

looking into src.zip, Math.hypot uses StrictMath, which is a native Method:

public static native double hypot(double x, double y);
Stefan W.
  • 17
  • 1