2010-09-18 5 views
10

He estado leyendo sobre Haskell y me cuesta entender cómo se manejan las definiciones de funciones en este lenguaje.¿Por qué sum x y es del tipo (Num a) => a -> a -> a en Haskell?

Digamos que estoy definiendo una función sum:

let sum x y = x + y 

si consulto Haskell para su tipo

:t sum 

consigo

sum :: (Num a) => a -> a -> a 
  1. ¿Qué significa el operador =>? ¿Tiene algo que ver con las expresiones lambda? Así es como se señala que lo que sigue al operador => es uno, en C#.
  2. ¿Qué significa a -> a -> a? A través de la inspección ocular en una serie de funciones diferentes que he estado probando, parece que el a -> a inicial son los argumentos y el -> a final es el resultado de la función suma. Si eso es correcto, ¿por qué no algo como (a, a) -> a, que parece mucho más intuitivo?
+12

Si desea Comprenda Haskell, será mejor que obtenga una (buena) presentación en lugar de preguntarle SO a cada parte del idioma. http://stackoverflow.com/questions/1012573/how-to-learn-haskell/1016986#1016986 enlaces a algunos buenos recursos y tiene más consejos. – delnan

Respuesta

29

0. El Haskell => no tiene nada que ver con C# 's =>.En Haskell una función anónima se crea con

\x -> x * x 

Además, no se nombre a la función sum porque such a function ya existe en el preludio. Vamos a llamarlo plus a partir de ahora para evitar confusiones.

1. De todos modos, el => en Haskell proporciona un contexto para el tipo. Por ejemplo:

show :: (Show a) => a -> String 

Aquí, el Show a => significa a tipo debe ser una instancia de la clase tipoShow, lo que significa a debe ser convertible en una cadena. Del mismo modo, (Num a) => a -> a -> a significa que el tipo a debe ser una instancia del tipo clase Num, lo que significa a debe ser como un número. Esto impone una restricción en a para que show o plus no acepten alguna entrada no admitida, p. plus "56" "abc". (La cadena no es como un número.)

Una clase de tipo es similar a la interfaz de C#, o más específicamente, a interface base-type constraint in generics. Consulte la pregunta Explain Type Classes in Haskell para obtener más información.

2.a -> a -> a significa a -> (a -> a). Por lo tanto, en realidad es una función unaria que devuelve otra función.

plus x = \y -> x + y 

Esto hace que la aplicación parcial (currying) sea muy fácil. La aplicación parcial se usa mucho especialmente cuando se usan funciones de orden superior. Por ejemplo podríamos utilizar

map (plus 4) [1,2,3,4] 

añadir 4 a cada elemento de la lista. De hecho, podríamos volver a utilizar la aplicación parcial de definir:

plusFourToList :: Num a => [a] -> [a] 
plusFourToList = map (plus 4) 

Si una función se escribe en la forma (a,b,c,...)->z por defecto, habría que introducir una gran cantidad de lambdas:

plusFourToList = \l -> map(\y -> plus(4,y), l) 
+2

Buena respuesta, excepto que siempre me encoge cuando escucho a alguien decir que las clases de tipo son como clases de interfaz. Tienen un rol un tanto análogo, pero razonar a partir de esa analogía te llevará por mal camino. En particular, en Haskell una clase de tipo no es en sí misma un tipo, por lo que no puede escribir "[Num]" para referirse a una lista de números. –

+2

Aunque escribir (Num a) => [a] no es realmente tan diferente. Creo que, siempre y cuando deje en claro que la analogía de la interfaz es sencilla para ayudarlo a comenzar, está bien. –

+1

Incluso si C# tenía una interfaz 'Num', algo como' List 'todavía es bastante diferente de' (Num a) => [a] ', porque' [5 :: Int, 3.5 :: Double] 'no es un valor válido de tipo '(Num a) => [a]' en Haskell. Creo que esto sorprende a muchas personas cuando ven por primera vez las clases de tipos. – hzap

4

en lugar de a -> a -> a en lugar de (a, a) -> a debido a currying. Dato curioso: ¡Curry fue (re) inventado por Haskell Curry! Básicamente significa que si proporciona un argumento uno obtendrá otra función de tipo a -> a, una aplicación parcial de suma.

+0

Incluso después de leer la introducción al artículo de Wikipedia, no puedo entender cuál es el punto de currying :( –

+1

¡No entiendo el punto! Es solo una de esas cosas que * haces * en la programación funcional ... – fredley

+2

Usted curry funciones para que pueda aplicarlos parcialmente. ¿Desea agregar 3 a cada elemento en una lista? No hay problema! - mapa (+3) [1, 2, 3, 4, 5] – Paul

5

Num a => medios "en la siguiente, a deberán referirse a un tipo que es una instancia de la clase de tipos Num" (que es un poco como una interfaz para tipos de número). El operador => separa las "restricciones de clase de tipo" del "cuerpo" del tipo. Es algo así como el operador where para restricciones genéricas en C#. Puede leerlo como una implicación lógica como "si a es un tipo numérico, entonces sum puede usarse con el tipo a -> a -> a".

a -> a -> a significa "una función que toma una a y devuelve una función que toma una a y devuelve un a". Para que tenga sentido, debe comprender que sum x y analiza como (sum x) y.

En otras palabras: primero llama al sum con el argumento x. A continuación, recupera una nueva función del tipo a -> a. A continuación, llama a esa función con el argumento y y ahora recupera una función de tipo a, donde a es el tipo de x y y y debe ser una instancia de la clase de tipo Num.

Si desea que sum tenga el tipo Num a => (a,a) -> a, puede definirlo como sum (x,y) = x+y. En este caso, tiene una función que toma una tupla que contiene dos a sy devuelve un a (donde a es nuevamente una instancia de la clase de tipo Num).

Sin embargo, el "estilo de curry" (funciones que devuelven funciones para simular múltiples parámetros) se usa mucho más a menudo que el estilo de tupla porque le permite aplicar funciones de manera sencilla. Ejemplo map (sum 5) [1,2,3]. Si hubiera definido sum con una tupla, tendría que hacer map (\y -> sum 5 y) [1,2,3].

10

Esto se debe a

Cada función en Haskell toma un único parámetro dey devuelve un único valor

Si una función tiene que tener múltiples valores, la función habría sido un curry función o tiene que tomar una sola tupla .

Si añadimos un paréntesis, la firma de la función se convierte en:

sum :: (Num a) => a -> (a -> a) 

En Haskell, la firma de la función: A -> B significa una función de "dominio" de la función es A y el "codominio" de la función es B; o en el lenguaje de un programador, la función toma un parámetro del tipo A y devuelve un valor de tipo B.

Por lo tanto, la definición de función sum :: Num -> (Num -> Num) significa que suma es "una función que toma un parámetro del tipo a y devuelve una función del tipo Num -> Num".

En efecto, esto lleva a la función currying/partial.

El concepto de currificación es esencial en los lenguajes funcionales como Haskell, ya que tendrá que hacer cosas como:

map (sum 5) [1, 2, 3, 5, 3, 1, 3, 4] -- note: it is usually better to use (+ 5) 

En ese código, (suma 5) es una función que toma un único parámetro, este función (suma 5) se llamará para cada elemento en la lista, por ejemplo ((Suma 5) 1) devuelve 6.

Si sum tenían una firma de sum :: (Num, Num) -> Num, a continuación, suma tendría que recibir tanto de su parámetro, al mismo tiempo, porque ahora suma es una "función que recibe un tuple (Num, Num) y devuelve una Num ".

Ahora, la segunda pregunta, ¿qué significa Num a => a -> a? Básicamente es una forma abreviada de decir que cada vez que veas a en la firma, reemplázalo con Num o con una de sus clases derivadas.

+2

Está abusando de la notación un poco: 'Num' es una clase de tipo, no un tipo. – dave4420

Cuestiones relacionadas