Assim como o título diz: que garantias existem para que uma unidade de retorno da função Haskell seja avaliada? Alguém poderia pensar que não há necessidade de executar qualquer tipo de avaliação nesse caso; o compilador poderia substituir todas essas chamadas por um ()
valor imediato, a menos que existam solicitações explícitas de rigor; nesse caso, o código pode ter que decidir se deve retorno ()
ou fundo.
Eu experimentei isso no GHCi, e parece que acontece o contrário, ou seja, essa função parece ser avaliada. Um exemplo muito primitivo seria
f :: a -> ()
f _ = undefined
A avaliação f 1
gera um erro devido à presença de undefined
, portanto, alguma avaliação definitivamente acontece. Não está claro até que ponto a avaliação é profunda; Às vezes, parece ser tão profundo quanto necessário para avaliar todas as chamadas para funções retornando ()
. Exemplo:
g :: [a] -> ()
g [] = ()
g (_:xs) = g xs
Este código faz um loop indefinidamente se apresentado g (let x = 1:x in x)
. Mas então
f :: a -> ()
f _ = undefined
h :: a -> ()
h _ = ()
pode ser usado para mostrar que h (f 1)
retornos ()
; portanto, neste caso, nem todas as subexpressões com valor unitário são avaliadas. Qual é a regra geral aqui?
ETA: é claro que eu sei sobre preguiça. Estou perguntando o que impede os escritores de compiladores de tornar esse caso em particular ainda mais preguiçoso do que o normalmente possível.
ETA2: resumo dos exemplos: O GHC parece tratar ()
exatamente como qualquer outro tipo, ou seja, como se houvesse uma pergunta sobre qual valor regular que habita o tipo deve ser retornado de uma função. O fato de existir apenas um desses valores não parece ser (ab) usado pelos algoritmos de otimização.
ETA3: quando digo Haskell, quero dizer Haskell como definido pelo relatório, não Haskell-the-H-in-GHC. Parece ser uma suposição não compartilhada tão amplamente quanto eu imaginava (que era "em 100% dos leitores"), ou eu provavelmente teria sido capaz de formular uma pergunta mais clara. Mesmo assim, lamento alterar o título da pergunta, pois ela originalmente perguntou quais garantias existem para que uma função seja chamada.
ETA4: parece que essa pergunta seguiu seu curso, e eu estou considerando isso sem resposta. (Eu estava procurando por uma função de 'pergunta próxima', mas só encontrei 'responda sua própria pergunta' e, como ela não pode ser respondida, não segui esse caminho.) , que sou tentado a interpretar como uma resposta forte, mas não definitiva, 'sem garantia para o idioma em si'. Tudo o que sabemos é que a implementação atual do GHC não ignorará a avaliação de uma função desse tipo.
Eu encontrei o problema real ao portar um aplicativo OCaml para Haskell. O aplicativo original tinha uma estrutura mutuamente recursiva de muitos tipos, e o código declarou várias funções chamadas assert_structureN_is_correct
N em 1 .. 6 ou 7, cada uma das quais retornou a unidade se a estrutura estivesse realmente correta e lançou uma exceção se não estivesse. . Além disso, essas funções se chamavam ao decompor as condições de correção. Em Haskell, isso é melhor tratado com a Either String
mônada, então transcrevi dessa maneira, mas a questão como questão teórica permaneceu. Obrigado por todas as entradas e respostas.
fonte
h1::()->() ; h1 () = ()
eh2::()->() ; h2 _ = ()
. Execute ambosh1 (f 1)
eh2 (f 1)
, e veja que apenas o primeiro exige(f 1)
.f 1
é "substituído" porundefined
em todos os casos.... -> ()
pode 1) terminar e retornar()
, 2) terminar com um erro de exceção / tempo de execução e falhar ao retornar qualquer coisa ou 3) divergir (recursão infinita). O GHC não otimiza o código, assumindo que apenas 1) pode acontecer: sef 1
for exigido, não pula sua avaliação e retorna()
. A semântica de Haskell é avaliá-la e ver o que acontece entre 1,2,3.()
(o tipo ou o valor) nesta questão. Todas as mesmas observações acontecem se você substituir() :: ()
por, por exemplo,0 :: Int
todos os lugares. Essas são apenas velhas consequências chatas da avaliação preguiçosa.()
tipo,()
eundefined
.Respostas:
Você parece assumir que o tipo
()
tem apenas um valor possível()
e, portanto, espera que qualquer chamada de função retorne um valor do tipo()
seja automaticamente assumida para realmente produzir o valor()
.Não é assim que Haskell funciona. Todo tipo tem mais um valor em Haskell, ou seja, nenhum valor, erro ou o chamado "fundo", codificado por
undefined
. Assim, uma avaliação está realmente ocorrendo:é equivalente ao idioma principal
ou mesmo (*)
e o Core
_Case
está forçando :O valor é forçado à forma normal da cabeça fraca. Isso faz parte da definição de idioma.
Haskell não é uma linguagem de programação declarativa .
(*)
print x = putStr (show x)
eshow () = "()"
, portanto, oshow
chamada possa ser totalmente compilada.O valor é de fato conhecido antecipadamente como
()
, e mesmo o valor deshow ()
é conhecido como adiantado"()"
. Mesmo assim, a semântica de Haskell aceita exige que o valor de(f 1)
seja forçado a ficar com a cabeça normal antes de continuar imprimindo a string conhecida anteriormente"()"
.editar: considere
concat (repeat [])
. Deveria ser[]
, ou deveria ser um loop infinito?A resposta de uma "linguagem declarativa" é provavelmente
[]
. A resposta de Haskell é o loop infinito .A preguiça é a "programação declarativa do pobre homem", mas ainda não é a coisa real .
edit2 :
print $ h (f 1) == _Case (h (f 1)) _Of () -> print ()
e somenteh
é forçado, nãof
; e produzir sua respostah
não precisa forçar nada, de acordo com sua definiçãoh _ = ()
.observações de despedida: Preguiça pode ter razão de ser, mas não é sua definição. Preguiça é o que é. É definido como todos os valores inicialmente sendo thunks que são forçados ao WHNF de acordo com as demandas provenientes
main
. Se isso ajuda a evitar o fundo em um determinado caso específico, de acordo com suas circunstâncias específicas, é o que faz. Se não, não. Isso é tudo.Ajuda a implementá-lo você mesmo, no seu idioma favorito, para ter uma ideia. Mas também podemos rastrear a avaliação de qualquer expressão nomeando cuidadosamente todos os valores intermediários à medida que eles surgirem.
Indo pelo relatório , temos
então
e com
Isto continua
Agora, a seção 3.17.3 Semântica formal da correspondência de padrões do relatório diz
e caso
(r)
na Figura 3.2 afirma()
é construtor de dados da arity 0, portanto é o mesmo quee o resultado geral da avaliação é assim
⊥
.fonte
case
Core, na verdade, e estava ignorando o buraco. :) Eu editei para mencionar o Core.show
invocada porprint
? Algo comoshow x = case x of () -> "()"
case
no Core, não no próprio Haskell. Haskell é traduzido para o Core, que possui umcase
AFAIK forçador. Você está certo de quecase
Haskell não está forçando por si só. Eu poderia escrever algo em Scheme ou ML (se eu pudesse escrever ML), ou pseudocódigo.print
força o quanto for necessário para imprimir. ele não olha para o tipo, os tipos desaparecem, são apagados, quando o programa é executado, a sub-rotina de impressão correta já é escolhida e compilada, de acordo com o tipo, no momento da compilação; essa sub-rotina ainda forçará seu valor de entrada para WHNF no tempo de execução e, se não for definido, causará um erro.