2009-01-27 8 views
24

El cuadrado de la hipotenusa de un triángulo rectángulo es igual a la suma de los cuadrados en los otros dos lados.¿Cómo se escribe el Teorema de Pitágoras en Scala?

Éste es el teorema de Pitágoras. Una función para calcular la hipotenusa basada en la longitud "a" y "b" de sus lados devolvería sqrt (a * a + b * b).

La pregunta es: ¿cómo definiría una función de este tipo en Scala de forma tal que pueda usarse con cualquier tipo que implemente los métodos apropiados?

Para el contexto, imagina toda una biblioteca de teoremas matemáticos que quieras usar con los tipos Int, Double, Int-Rational, Double-Rational, BigInt o BigInt-Rational dependiendo de lo que estés haciendo, y la velocidad, precisión , precisión y requisitos de rango.

+2

Y ahora sé por qué los tipos estructurales no me permiten hacerlo: http://article.gmane.org/gmane.comp.lang.scala/7013 –

Respuesta

24

Esto sólo funciona en Scala 2.8, pero funciona:

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 

De manera más general, el rasgo Numeric es efectivamente una referencia sobre cómo resolver este tipo de problema. Vea también Ordering.

+0

Puede actualizar esta popular respuesta para usar los límites de contexto, ahora que existen. –

+0

@BrianMcCutchon Creo que los límites de contexto estaban de hecho disponibles en 2.8, pero luego tengo que asignarlos a una variable de todos modos para que pueda importar 'mkNumericOps' de ella. Sin embargo, hay mejores soluciones ahora, aunque, si lo desea, formule una pregunta por separado. –

+1

¿Te refieres a 'import Numeric.Implicits._'? Eso le permitiría usar límites de contexto sin una variable de evidencia. –

18

La manera más obvia:

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) 

Esto es horrible por muchas razones. Primero, tenemos el problema del tipo recursivo, Num. Esto solo está permitido si compila este código con la opción -Xrecursive establecida en un valor entero (5 es probablemente más que suficiente para los números). Segundo, el tipo Num es estructural, lo que significa que cualquier uso de los miembros que define se compilará en las correspondientes invocaciones reflexivas. En pocas palabras, esta versión de pyth es obscenamente ineficiente, corriendo en el orden de varios cien mil veces más lento que una implementación convencional. Sin embargo, no hay forma de evitar el tipo de estructura si desea definir pyth para cualquier tipo que defina +, * y para la cual exista una función sqrt.

Finalmente, llegamos a la cuestión más fundamental: es demasiado complicado. ¿Por qué molestarse en implementar la función de esta manera? Hablando en términos prácticos, los únicos tipos a los que tendrá que aplicar son los números reales de Scala. Por lo tanto, es más fácil simplemente hacer lo siguiente:

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

¡Todos los problemas resueltos! Esta función se puede utilizar en los valores del tipo Double, Int, Float, incluso los impares como Short gracias a las maravillas de la conversión implícita. Si bien es cierto que esta función es técnicamente menos flexible que nuestra versión de tipo estructural, es ampliamente más eficiente y eminentemente más legible. Es posible que hayamos perdido la capacidad de calcular el teorema de Pythagrean para tipos imprevistos que definen + y *, pero no creo que vaya a perder esa capacidad.

+2

¿Funciona la solución "simple" con BigNum o Rational? ¿Puedo definir una biblioteca completa de teoremas matemáticos y hacer que se utilicen ya sea por doble, entero, bignum o racional? –

+1

+1 tanto para la implementación como para el razonamiento por el cual no se debe hacer así. :-) –

+1

Ahora que estoy mucho más informado sobre Scala, veo que existe otra solución. Definir una clase Num abstracta, subclases para cualquier tipo deseado, conversiones implícitas de los tipos deseados a la subclase correspondiente y hacer que pyth [A] acepte "a" y "b" de A, más implícito de A => Num [A ] ¿Te importaría agregar esta solución a tu respuesta? Me gustaría aceptarlo, pero preferiría que sea más completo. –

0

Hay un método en java.lang.Math:

public static double hypot (double x, double y) 

para los que el javadocs afirma:

Devoluciones sqrt (x2 + y2) sin desbordamiento o subdesbordamiento intermedio.

mirar en src.zip, Math.hypot utiliza StrictMath, que es un método nativo:

public static native double hypot(double x, double y); 
+1

¿Cómo lo uso con Int o con BigDecimal? No me preocupa calcular el hipotenuso, me preocupa cómo hago las matemáticas genéricas. –

+0

Lo siento. Solo debería ser una nota al margen, entonces. –

2

Reflexiones sobre la respuesta de Daniel:

he experimented generalizar Numeric a Real , que sería más apropiado para esta función para proporcionar la función sqrt. Esto daría como resultado:

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

Es complicado, pero posible, utilizar números literales en tales funciones genéricas.

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 
} 

La inferencia de tipos funciona mejor si el sqrt se pasa en una segunda lista de parámetros.

Los parámetros a y b se pasarán como Objetos, pero @specialized podría solucionarlo. Desafortunadamente todavía habrá algunos gastos generales en las operaciones de matemáticas.

Puede casi hacer sin la importación de mkNumericOps. Obtuve frustratringly close!

+0

Por supuesto, 'n' tiene un' uno'. Y un 'cero'. –

+0

¡Uno y cero deberían ser números suficientes para cualquiera! :) – retronym

+3

Me gustaría * e *, * π * y * i * también, así que puedo expresar la Identidad de Euler. –

Cuestiones relacionadas