Por que as funções em F # e Ocaml (e possivelmente em outras linguagens) não são recursivas por padrão?
Em outras palavras, por que os designers da linguagem decidiram que era uma boa ideia fazer você digitar explicitamente rec
uma declaração como:
let rec foo ... = ...
e não dá a função recursiva capacidade por padrão? Por que a necessidade de uma rec
construção explícita ?
Respostas:
Os descendentes franceses e britânicos do ML original fizeram escolhas diferentes e suas escolhas foram herdadas ao longo das décadas pelas variantes modernas. Portanto, isso é apenas um legado, mas afeta os idiomas nessas linguagens.
As funções não são recursivas por padrão na família de idiomas CAML francesa (incluindo OCaml). Essa escolha torna mais fácil substituir as definições de função (e variável) usando
let
nessas linguagens, porque você pode consultar a definição anterior dentro do corpo de uma nova definição. F # herdou essa sintaxe de OCaml.Por exemplo, substituindo a função
p
ao calcular a entropia de Shannon de uma sequência em OCaml:Observe como o argumento
p
para ashannon
função de ordem superior é substituído por outrop
na primeira linha do corpo e depois por outrop
na segunda linha do corpo.Por outro lado, a ramificação SML britânica da família de linguagens ML escolheu a outra escolha e as
fun
funções de ligação da SML são recursivas por padrão. Quando a maioria das definições de função não precisa de acesso às ligações anteriores de seu nome de função, isso resulta em um código mais simples. No entanto, as funções superada são feitos para usar nomes diferentes (f1
,f2
etc.) que polui o alcance e torna possível acidentalmente invocar a "versão" errada de uma função. E agora há uma discrepância entrefun
funções vinculadas implicitamente recursivas e funções vinculadas não recursivasval
.Haskell torna possível inferir as dependências entre as definições, restringindo-as a serem puras. Isso faz com que as amostras de brinquedos pareçam mais simples, mas tem um custo alto em outro lugar.
Observe que as respostas dadas por Ganesh e Eddie são pistas falsas. Eles explicaram por que grupos de funções não podem ser colocados dentro de um gigante,
let rec ... and ...
porque isso afeta quando as variáveis de tipo são generalizadas. Isso não tem nada a ver comrec
ser padrão no SML, mas não no OCaml.fonte
Uma razão crucial para o uso explícito de
rec
é fazer com a inferência de tipo de Hindley-Milner, que fundamenta todas as linguagens de programação funcional de tipo estático (embora alterado e estendido de várias maneiras).Se você tem uma definição
let f x = x
, espera que ela tenha um tipo'a -> 'a
e seja aplicável a diferentes'a
tipos em diferentes pontos. Mas, da mesma forma, se você escreverlet g x = (x + 1) + ...
, esperariax
ser tratado como umint
no resto do corpo deg
.A maneira como a inferência de Hindley-Milner lida com essa distinção é por meio de uma etapa de generalização explícita . Em certos pontos ao processar seu programa, o sistema de tipo para e diz "ok, os tipos dessas definições serão generalizados neste ponto, de modo que quando alguém as usar, quaisquer variáveis de tipo livre em seu tipo serão novamente instanciadas, e assim não interferirá com nenhum outro uso desta definição. "
Acontece que o melhor lugar para fazer essa generalização é verificar um conjunto recursivo de funções mutuamente. Qualquer coisa antes, e você generalizará demais, levando a situações em que os tipos podem realmente colidir. Posteriormente, você generalizará muito pouco, fazendo definições que não podem ser usadas com instanciações de vários tipos.
Portanto, visto que o verificador de tipo precisa saber quais conjuntos de definições são recursivos mutuamente, o que ele pode fazer? Uma possibilidade é simplesmente fazer uma análise de dependência em todas as definições em um escopo e reordená-las nos menores grupos possíveis. Haskell realmente faz isso, mas em linguagens como F # (e OCaml e SML), que têm efeitos colaterais irrestritos, é uma má ideia porque pode reordenar os efeitos colaterais também. Em vez disso, ele pede ao usuário para marcar explicitamente quais definições são recursivas mutuamente e, portanto, por extensão, onde a generalização deve ocorrer.
fonte
rec
mas SML não, é um contra-exemplo óbvio. Se a inferência de tipo fosse o problema pelos motivos que você descreve, OCaml e SML não poderiam ter escolhido soluções diferentes como fizeram. A razão, claro, é que você está falandoand
para tornar Haskell relevante.Existem dois motivos principais pelos quais esta é uma boa ideia:
Primeiro, se você habilitar definições recursivas, não poderá se referir a uma ligação anterior de um valor com o mesmo nome. Geralmente, esse é um idioma útil quando você está fazendo algo como estender um módulo existente.
Em segundo lugar, os valores recursivos, e especialmente os conjuntos de valores recursivos mutuamente, são muito mais difíceis de raciocinar do que as definições que procedem em ordem, cada nova definição construída sobre o que já foi definido. É bom, ao ler esse código, ter a garantia de que, exceto para as definições marcadas explicitamente como recursivas, as novas definições só podem se referir a definições anteriores.
fonte
Algumas suposições:
let
não é usado apenas para vincular funções, mas também outros valores regulares. A maioria das formas de valores não podem ser recursivas. Certas formas de valores recursivos são permitidas (por exemplo, funções, expressões lazy, etc.), portanto, é necessária uma sintaxe explícita para indicar isso.let
construção é semelhante àlet
construção em Lisp e Scheme; que são não recursivos. Há umaletrec
construção separada no Scheme para recursiva vamosfonte
let rec xs = 0::ys and ys = 1::xs
.Dado isso:
Comparar:
Com isso:
O primeiro redefine
f
para aplicar o definido anteriormentef
ao resultado da aplicaçãog
aa
. Este último redefinef
a aplicação de loop para sempreg
aa
, o que geralmente não é o que você quer em ML variantes.Dito isso, é uma coisa do estilo do designer de linguagem. Apenas vá em frente.
fonte
Uma grande parte disso é que ele dá ao programador mais controle sobre a complexidade de seus escopos locais. O espectro de
let
,let*
elet rec
oferta de um nível crescente de poder e custo.let*
elet rec
são, em essência, versões aninhadas do simpleslet
, portanto, usar qualquer um deles é mais caro. Essa classificação permite que você microgerencie a otimização do seu programa, pois você pode escolher o nível de permissão de que precisa para a tarefa em questão. Se você não precisa de recursão ou da capacidade de se referir a vínculos anteriores, pode recorrer a um simples let para economizar um pouco de desempenho.É semelhante aos predicados de igualdade graduada em Scheme. (ou seja
eq?
,eqv?
eequal?
)fonte