Expresiones lambda sin parámetros en Haskell y / o cálculo lambda

9

En lenguajes ansiosos como Scheme y Python, puede usar una expresión lambda sin parámetros para retrasar la evaluación, por ejemplo, en Scheme (Chicken Scheme):

#;1> (define (make-thunk x) (lambda () (+ x 1)))
#;2> (define t (make-thunk 1))
#;3> (t)
2

En la línea 2, testá vinculada a la expresión no evaluada (lambda () (+ 1 1)), que luego se evalúa 2en la línea 3.

Del mismo modo, en Python:

>>> def make_thunk(x): return lambda: x + 1
... 
>>> t = make_thunk(1)
>>> t()
2

Usando esta técnica, se puede implementar una evaluación perezosa en un lenguaje entusiasta.

Entonces, esperaba que Haskell no tuviera expresiones lambda sin parámetros porque el lenguaje ya es vago y no hay necesidad de construir expresiones retrasadas. Para mi sorpresa, descubrí que en Haskell es posible escribir la expresión lambda

\() -> "s"

que solo se puede aplicar al ()valor de la siguiente manera:

(\() -> "s") ()

dando el resultado

"s"

La aplicación de esta función a cualquier argumento que no sea ()arroja una excepción (al menos por lo que pude ver durante mis pruebas). Esto parece diferente de la evaluación retrasada en Scheme y Python, porque la expresión todavía necesita un argumento para ser evaluado. Entonces, ¿qué significa una expresión lambda sin variables (como \() -> "s") en Haskell y para qué puede ser útil?

Además, me gustaría saber si existen expresiones lambda sin parámetros similares en (alguna variedad de) cálculo lambda.

Giorgio
fuente
"La aplicación de esta función a cualquier argumento que no sea ()arrojar una excepción ..." ¿Hace que el programa arroje una excepción o el compilador se queja de que el código no escribe check?
Fried Brice

Respuestas:

12

Bueno, las otras respuestas cubren lo que \() -> "something"significa en Haskell: una función unaria que toma ()como argumento.

  • ¿Qué es una función sin argumentos? - Un valor. En realidad, en ocasiones puede ser útil pensar en las variables como funciones nulares que evalúan su valor. La letsintaxis de una función sin argumentos (que en realidad no existe) termina dándole un enlace variable:let x = 42 in ...

  • ¿El cálculo lambda tiene funciones nulares? - No. Cada función toma exactamente un argumento. Sin embargo, este argumento puede ser una lista o la función puede devolver otra función que tome el siguiente argumento. Haskell prefiere la última solución, por lo que en a b crealidad son dos llamadas a funciones ((a b) c). Para simular funciones nulares, debe pasar un valor de marcador de posición no utilizado.

amon
fuente
1
+1 para "¿Qué es una función sin argumentos? - Un valor" y, por extensión, también la vista de función nular. Creo que es útil poder ver cómo se relacionan las funciones y los valores, y una función puede verse como una generalización de un valor.
KChaloux
@amon: Entonces, una función nulary solo tiene sentido en un lenguaje como Scheme en el que puede activar explícitamente la evaluación y en el que las funciones (procedimientos) pueden ser útiles tanto por su valor como por sus efectos secundarios.
Giorgio
@Giorgio Lisp es una codificación directa del cálculo lambda con mucho azúcar sintáctico, pero si es cálculo lambda, no puede tener funciones nulares. Todo es una lista, por lo que la expresión de la aplicación de función (f)es conceptualmente una lista de un solo elemento, con un valor de encabezado de 'fy una cola nula, por lo que utilizando conspodemos escribirlo como (cons 'f '()). Esta cola es la lista que (conceptualmente) se usa como argumento, y no importa que nil represente la lista vacía. La diferencia entre Lisp y Haskell es que este último tiene un currículum implícito, por lo que la expresión (f)significa cosas diferentes
amon
2
Pero sí, las funciones sin argumentos generalmente no son útiles en Haskell, porque son vagas y puras. Los lenguajes estrictos pueden usar funciones sin argumentos para simular pereza o para devoluciones de llamada de propósito general. Si realmente tiene que proporcionar un argumento ficticio como ()(como es el caso en ML) o si no lo hace a sabiendas (como es el caso en Lisps o lenguajes similares a C) realmente no importa.
amon
9

Estás malinterpretando lo que ()significa en Haskell. No es la falta de un valor, es más bien el único valor del tipo Unidad (el tipo en sí se refiere a un paréntesis vacío ()).

Dado que las lambdas se pueden construir para utilizar la coincidencia de patrones, la expresión lambda \() -> "s"dice explícitamente "crear una función anónima, esperando una entrada que coincida con el ()patrón". No tiene mucho sentido hacerlo, pero ciertamente está permitido.

También puede usar la coincidencia de patrones con lambdas de otras maneras, por ejemplo:

map (\(a, b) -> a + b) [(1,2), (3,4), (5,6)] -- uses pattern matching to destructured tuples

map (\(Name first _) -> first) [Name "John" "Smith", Name "Jane" "Doe"] -- matches a "Name" data type and its first field

map (\(x:_) -> x) [[1,2,3], [4,5,6]] -- matches the head of a list
KChaloux
fuente
Excepto que Unittambién se deletrea ()en Haskell.
@delnan Soy más competente en Scala = P Actualizaré la respuesta para ser un poco más específico en ese punto.
KChaloux
Así que me imagino que este mecanismo de coincidencia de patrones es una característica de Haskell que no está presente en el cálculo lambda (?)
Giorgio
@Giorgio No estoy muy interesado en mi cálculo lambda básico, pero entiendo que tienes razón; la coincidencia de patrones es una característica del lenguaje y no es innata al cálculo lambda.
KChaloux
1
@Giorgio Suena bien. Creo que puedes fijar esto en cómo cada idioma trata los efectos secundarios. Una función sin parámetros es A) un valor invariable o B) algo que tiene un efecto secundario. Haskell intenta evitar los efectos secundarios más que Python (o Scheme en menor medida), por lo que supone que una función sin parámetros es un valor. No tiene mucho sentido proporcionar una función anónima que no recibe entrada y devuelve el mismo valor (a diferencia de const, que toma cualquier entrada y la transforma en el mismo valor).
KChaloux
1

() -> "s"es una función lambda que toma un argumento:

ghci> :t (\() -> "s")
(\() -> "s") :: () -> [Char]

(), la tupla vacía (a veces conocida como Unidad), es un tipo completo en Haskell. Solo tiene un miembro (ignorando _|_*), que también se escribe como (). Mira la definición de ():

data () = ()

Esto significa que el formulario en el lado izquierdo de su expresión lambda es sintácticamente una coincidencia de patrón que coincide (), el único miembro del tipo (). Solo hay una forma válida de llamarlo (que es proporcionar ()como argumento) porque solo hay un miembro válido del tipo ().

Tal función no es muy útil en Haskell, porque de forma predeterminada los términos se evalúan perezosamente de todos modos, como observó en su pregunta.

Para una explicación mucho más detallada, vea esta respuesta .

* _|_se llama "fondo" o "indefinido". Siempre verifica los tipos, pero bloquea su programa. El ejemplo clásico de cómo obtener un _|_es let x = x in x.

Benjamin Hodgson
fuente
"Dicha función no es muy útil en Haskell, porque de forma predeterminada los términos se evalúan perezosamente de todos modos, como observó en su pregunta". .
Giorgio