Expressões lambda sem parâmetros no cálculo de Haskell e / ou lambda

9

Em linguagens ansiosas como Scheme e Python, você pode usar uma expressão lambda sem parâmetros para atrasar a avaliação, por exemplo, em Scheme (Chicken Scheme):

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

Na linha 2, té vinculado à expressão não avaliada (lambda () (+ 1 1)), que é avaliada 2na linha 3.

Da mesma forma, em Python:

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

Usando essa técnica, é possível implementar uma avaliação lenta em um idioma ansioso.

Então, eu esperava que Haskell não tivesse expressões lambda sem parâmetros, porque a linguagem já é preguiçosa e não há necessidade de criar expressões atrasadas. Para minha surpresa, descobri que em Haskell é possível escrever a expressão lambda

\() -> "s"

que só pode ser aplicado ao ()valor da seguinte forma:

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

dando o resultado

"s"

A aplicação desta função a qualquer argumento que não seja ()lança uma exceção (pelo menos até onde pude ver durante meus testes). Isso parece diferente da avaliação atrasada em Scheme e Python, porque a expressão ainda precisa de um argumento para ser avaliado. Então, o que uma expressão lambda sem variáveis ​​(como \() -> "s") significa em Haskell e para que ela pode ser útil?

Além disso, eu ficaria curioso para saber se existem expressões lambda sem parâmetros semelhantes em (alguma variedade de) cálculo lambda.

Giorgio
fonte
"A aplicação desta função a qualquer argumento que não ()gera uma exceção ..." Faz com que o programa lance uma exceção ou faz com que o compilador reclame que o código não digita check?
Fried Brice

Respostas:

12

Bem, as outras respostas cobrem o que \() -> "something"significa em Haskell: uma função unária que toma ()como argumento.

  • O que é uma função sem argumentos? - Um valor. Na verdade, ocasionalmente pode ser útil pensar em variáveis ​​como funções nulas que avaliam seu valor. A letsintaxe-para uma função sem argumentos (que na verdade não existe) acaba fornecendo uma associação de variável:let x = 42 in ...

  • O cálculo lambda tem funções nulas? - Não. Toda função usa exatamente um argumento. No entanto, esse argumento pode ser uma lista ou a função pode retornar outra função que aceita o próximo argumento. Haskell prefere a última solução, de modo que a b cna verdade são duas chamadas de função ((a b) c). Para simular funções nulas, você deve passar algum valor de espaço reservado não utilizado.

amon
fonte
1
+1 para "O que é uma função sem argumentos? - Um valor" e, por extensão, a exibição da função nula também. Eu acho que é útil poder ver como as funções e os valores estão relacionados, e uma função pode ser vista como uma generalização de um valor.
KChaloux
@amon: Portanto, uma função nula só faz sentido em uma linguagem como o Scheme, na qual você pode acionar explicitamente a avaliação e em que funções (procedimentos) podem ser úteis tanto pelo valor quanto pelos efeitos colaterais.
Giorgio
@Giorgio Lisp é uma codificação direta do cálculo lambda com muito açúcar sintático, mas se for cálculo lambda, não pode ter funções nulas. Tudo é uma lista; portanto, a expressão do aplicativo de função (f)é conceitualmente uma lista de elemento único, com um valor inicial 'fe uma cauda nula - para consque possamos escrever como (cons 'f '()). Essa cauda é a lista que é (conceitualmente) usada como argumento, e não importa que nil represente a lista vazia. A diferença entre Lisp e Haskell é que o último tem curtimenta implícita, de modo que a expressão (f)significa coisas diferentes
amon
2
Mas sim, funções sem argumentos geralmente não são úteis em Haskell, porque são preguiçosas e puras. Linguagens estritas podem usar funções sem argumento para simular preguiça ou para retornos de chamada de uso geral. Se você precisa fornecer um argumento fictício como ()(como é o caso nos MLs) ou se não o faz conscientemente (como é o caso nos idiomas Lisps ou C), não importa.
amon
9

Você está interpretando mal o que ()significa em Haskell. Não é a falta de um valor, é o único valor do tipo Unit (o tipo em si é referido por um conjunto vazio de parênteses ()).

Como os lambdas podem ser construídos para usar a correspondência de padrões, a expressão lambda \() -> "s"está dizendo explicitamente "crie uma função anônima, esperando uma entrada que corresponda ao ()padrão". Não há muito sentido em fazê-lo, mas certamente é permitido.

Também é possível usar a correspondência de padrões com lambdas de outras maneiras, por exemplo:

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
fonte
Exceto que Unittambém está escrito ()em Haskell.
@ delnan Eu sou mais proficiente em Scala = P Vou atualizar a resposta para ser um pouco mais específico nesse ponto.
KChaloux
Então eu imagino que este mecanismo de correspondência de padrões é uma característica Haskell que não está presente em lambda-cálculo (?)
Giorgio
@Giorgio Eu não estou super empolgado no meu cálculo lambda básico, mas meu entendimento é que você está correto; a correspondência de padrões é um recurso de idioma e não é inerente ao cálculo lambda.
KChaloux
1
@Giorgio Parece certo. Eu acho que você pode definir como cada idioma trata os efeitos colaterais. Uma função sem parâmetros é A) um valor imutável ou B) algo que tem um efeito colateral. Haskell tenta evitar efeitos colaterais mais do que Python (ou Scheme, em menor grau), de modo que assume que uma função sem parâmetros é um valor. Não faz muito sentido fornecer uma função anônima que não recebe entrada e retorna o mesmo valor (ao contrário de constque recebe qualquer entrada e transforma tudo no mesmo valor).
KChaloux
1

() -> "s"é uma função lambda que usa um argumento:

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

(), a tupla vazia (às vezes conhecida como Unidade), é um tipo completo em Haskell. Ele possui apenas um membro (ignorando _|_*), que também é escrito como (). Veja a definição de ():

data () = ()

Isso significa que o formulário no lado esquerdo da sua expressão lambda é sintaticamente uma correspondência de padrão que corresponde ()ao único membro do tipo (). Há apenas uma maneira válida de chamá-lo (que é fornecer ()como argumento) porque há apenas um membro válido do tipo ().

Essa função não é muito útil no Haskell, porque, por padrão, os termos são preguiçosamente avaliados de qualquer maneira, como você observou na sua pergunta.

Para uma explicação muito mais detalhada, consulte esta resposta .

* _|_é chamado "inferior" ou "indefinido". Sempre verifica o tipo, mas trava o seu programa. O exemplo clássico de como obter um _|_é let x = x in x.

Benjamin Hodgson
fonte
"Essa função não é muito útil em Haskell, porque, por padrão, os termos são preguiçosamente avaliados de qualquer maneira, como você observou na sua pergunta.": Não apenas isso: eu preciso fornecer um argumento, o que não preciso, por exemplo, em Esquema .
Giorgio