Tipos de Haskell frustran una simple función ‘promedio’

Estoy jugando con el principiante Haskell, y quería escribir una función promedio. Parecía la cosa más simple del mundo, ¿verdad?

Incorrecto.

Parece que el sistema de tipos de Haskell prohíbe que el promedio funcione en un tipo numérico genérico: puedo hacer que funcione en una lista de integrales, o una lista de fraccionales, pero no en ambos.

Yo quiero:

average :: (Num a, Fractional b) => [a] -> b average xs = ... 

Pero solo puedo obtener:

 averageInt :: (Integral a, Fractional b) => [a] -> b averageInt xs = fromIntegral (sum xs) / fromIntegral (length xs) 

o

 averageFrac :: (Fractional a) => [a] -> a averageFrac xs = sum xs / fromIntegral (length xs) 

y el segundo parece funcionar Hasta que intente pasar una variable.

 *Main> averageFrac [1,2,3] 2.0 *Main> let x = [1,2,3] *Main> :tx x :: [Integer] *Main> averageFrac x :1:0: No instance for (Fractional Integer) arising from a use of `averageFrac ' at :1:0-8 Possible fix: add an instance declaration for (Fractional Integer) In the expression: average x In the definition of `it': it = averageFrac x 

Aparentemente, Haskell es muy exigente con sus tipos. Eso tiene sentido. Pero no cuando ambos podrían ser [Num]

¿Me estoy perdiendo una aplicación obvia de RealFrac?

¿Hay alguna manera de forzar integrales en fraccionales que no se ahogue cuando obtiene una entrada fraccional?

¿Hay alguna forma de usar Either y either para hacer algún tipo de función promedio polimórfica que funcione en cualquier tipo de matriz numérica?

¿El sistema de tipos de Haskell prohíbe abiertamente que esta función exista?

Aprender Haskell es como aprender Cálculo. Es realmente complicado y se basa en montañas de teoría, y algunas veces el problema es tan complejo que ni siquiera sé lo suficiente como para formular la pregunta correctamente, por lo que cualquier idea será cálidamente aceptada.

(También, nota al pie: esto se basa en un problema de tarea. Todos están de acuerdo en que el promedio de Frac, arriba, obtiene puntos completos, pero tengo la sospecha de que hay una manera de hacerlo funcionar en arreglos integrales y fraccionarios)

Entonces, fundamentalmente, estás limitado por el tipo de (/):

 (/) :: (Fractional a) => a -> a -> a 

Por cierto, también quieres Data.List.genericLength

 genericLength :: (Num i) => [b] -> i 

Entonces, ¿qué le parece eliminar el contenido de Integra para algo más general?

 import Data.List average xs = realToFrac (sum xs) / genericLength xs 

que tiene solo una restricción Real (Int, Integer, Float, Double) …

 average :: (Real a, Fractional b) => [a] -> b 

Entonces eso tomará cualquier Real en cualquier Fracción.

Y tenga en cuenta que todos los carteles quedaron atrapados por los literales polimórficos numéricos en Haskell. 1 no es un número entero, es cualquier número.

La clase Real proporciona solo un método: la capacidad de convertir un valor en la clase Num en racional. Que es exactamente lo que necesitamos aquí.

Y por lo tanto,

 Prelude> average ([1 .. 10] :: [Double]) 5.5 Prelude> average ([1 .. 10] :: [Int]) 5.5 Prelude> average ([1 .. 10] :: [Float]) 5.5 Prelude> average ([1 .. 10] :: [Data.Word.Word8]) 5.5 

La pregunta ha sido muy bien respondida por Dons, pensé que podría agregar algo.

Al calcular el promedio de esta manera:

average xs = realToFrac (sum xs) / genericLength xs

Lo que su código hará es recorrer la lista dos veces, una para calcular la sum de sus elementos y una para obtener su longitud. Por lo que sé, GHC aún no puede optimizar esto y calcular tanto la sum como la longitud en una sola pasada.

No hace daño incluso cuando un principiante lo piensa y acerca de posibles soluciones, por ejemplo, la función promedio puede escribirse usando un doblez que computa tanto la sum como la longitud; en ghci:

 :set -XBangPatterns import Data.List let avg l=let (t,n) = foldl' (\(!b,!c) a -> (a+b,c+1)) (0,0) l in realToFrac(t)/realToFrac(n) avg ([1,2,3,4]::[Int]) 2.5 avg ([1,2,3,4]::[Double]) 2.5 

La función no se ve tan elegante, pero el rendimiento es mejor.

Más información sobre el blog de Dons:

http://donsbot.wordpress.com/2008/06/04/haskell-as-fast-asc-c-working-at-a-high-altitude-for-low-level-performance/

Como Dons ha hecho un buen trabajo respondiendo tu pregunta, voy a trabajar para cuestionar tu pregunta …

Por ejemplo, en su pregunta, donde primero ejecuta un promedio en una lista dada, obtiene una buena respuesta. Luego, toma lo que parece exactamente la misma lista , asígnalo a una variable, luego usa la función de la variable … que luego explota.

Lo que te has encontrado aquí es una configuración en el comstackdor, llamada DMR: la discriminación M onomórfica D areded. Cuando pasaste la lista directamente a la función, el comstackdor no hizo suposiciones sobre qué tipo eran los números, solo dedujo qué tipos podría estar basados ​​en el uso, y luego eligió uno una vez que ya no podía restringir el campo. Es algo así como el lado opuesto al tipado de patos, allí.

De todos modos, cuando asignó la lista a una variable, se activó el DMR. Dado que usted puso la lista en una variable, pero no dio pistas sobre cómo desea usarla, el DMR hizo que el comstackdor eligiera un tipo, en este caso, escogió uno que coincidía con el formulario y parecía encajar: Integer . Dado que su función no puede usar un entero en su operación / (necesita un tipo en la clase Fractional ), se queja mucho: no hay instancia de Integer en la clase Fractional . Hay opciones que puede configurar en GHC para que no fuerce sus valores en una sola forma (“monomórfico”, ¿entiendes?) Hasta que lo necesite, pero hace que los mensajes de error sean un poco más difíciles de adivinar.

Ahora, en otra nota, tuviste una respuesta a la respuesta de Dons que me llamó la atención:

La tabla de la última página de cs.ut.ee/~varmo/MFP2004/PreludeTour.pdf me indujo a error y me mostró que Floating NOT hereda propiedades de Real, y luego asumí que no compartirían ningún tipo en común.

Haskell hace tipos diferentes a los que está acostumbrado. Real y Floating son clases de tipos, que funcionan más como interfaces que clases de objetos. Te dicen lo que puedes hacer con un tipo que está en esa clase, pero eso no significa que un tipo no pueda hacer otras cosas, como tampoco tener una interfaz significa que una clase (estilo OO) no puede tener otros.

Aprender Haskell es como aprender Cálculo

Yo diría que aprender Haskell es como aprender sueco: hay muchas cosas pequeñas y simples (letras, números) que se ven y funcionan igual, pero también hay palabras que parecen significar una cosa, cuando en realidad significan algo más. Pero una vez que lo domines con fluidez, tus amigos habituales se sorprenderán de cómo puedes sacar estas extrañas cosas que hacen que las preciosas bellezas hagan trucos increíbles. Curiosamente, hay muchas personas involucradas en Haskell desde el principio, que también saben sueco. Tal vez esa metáfora es más que solo una metáfora …

 :m Data.List let list = [1..10] let average = div (sum list) (genericLength list) average 

Sí, el sistema de tipos de Haskell es muy exigente. El problema aquí es el tipo de fromIntegral:

 Prelude> :t fromIntegral fromIntegral :: (Integral a, Num b) => a -> b 

fromIntegral solo aceptará un Integral como, no ningún otro tipo de Num. (/), por otro lado solo acepta fracciones. ¿Cómo haces para que los dos trabajen juntos?

Bueno, la función sum es un buen comienzo:

 Prelude> :t sum sum :: (Num a) => [a] -> a 

Sum toma una lista de cualquier Num y devuelve un Num.

Tu próximo problema es la longitud de la lista. La longitud es un Int:

 Prelude> :t length length :: [a] -> Int 

Necesita convertir ese Int en un Num también. Eso es lo que hace Integral.

Entonces ahora tienes una función que devuelve un Num y otra función que devuelve un Num. Hay algunas reglas para la promoción tipo de números que puede consultar , pero básicamente en este momento está listo:

 Prelude> let average xs = (sum xs) / (fromIntegral (length xs)) Prelude> :t average average :: (Fractional a) => [a] -> a 

Vamos a probarlo:

 Prelude> average [1,2,3,4,5] 3.0 Prelude> average [1.2,3.4,5.6,7.8,9.0] 5.4 Prelude> average [1.2,3,4.5,6,7.8,9] 5.25