¿Por qué las funciones en F # y Ocaml (y posiblemente en otros lenguajes) no son recursivas por defecto?
En otras palabras, ¿por qué los diseñadores del lenguaje decidieron que era una buena idea hacerte escribir explícitamente recuna declaración como:
let rec foo ... = ...
y no dar a la función la capacidad recursiva por defecto? ¿Por qué la necesidad de una recconstrucción explícita ?

Respuestas:
Los descendientes franceses y británicos del ML original tomaron decisiones diferentes y sus elecciones se han heredado a lo largo de las décadas hasta las variantes modernas. Esto es solo un legado, pero afecta los modismos en estos idiomas.
Las funciones no son recursivas de forma predeterminada en la familia de idiomas CAML en francés (incluido OCaml). Esta opción facilita la sustitución de las definiciones de función (y variable) que se utilizan
leten esos lenguajes porque puede hacer referencia a la definición anterior dentro del cuerpo de una nueva definición. F # heredó esta sintaxis de OCaml.Por ejemplo, reemplazando la función
pal calcular la entropía de Shannon de una secuencia en OCaml:Observe cómo el argumento
pde lashannonfunción de orden superior es reemplazado por otropen la primera línea del cuerpo y luego por otropen la segunda línea del cuerpo.Por el contrario, la rama británica SML de la familia de lenguajes ML tomó la otra opción y las
funfunciones vinculadas de SML son recursivas de forma predeterminada. Cuando la mayoría de las definiciones de funciones no necesitan acceso a enlaces previos de su nombre de función, esto da como resultado un código más simple. Sin embargo, las funciones reemplazadas están hechas para usar diferentes nombres (f1,f2etc.) lo que contamina el alcance y hace posible invocar accidentalmente la "versión" incorrecta de una función. Y ahora hay una discrepancia entrefunfunciones vinculadas implícitamente recursivas y funciones vinculadas no recursivasval.Haskell hace posible inferir las dependencias entre definiciones restringiéndolas para que sean puras. Esto hace que las muestras de juguetes parezcan más simples, pero tiene un costo elevado en otros lugares.
Tenga en cuenta que las respuestas dadas por Ganesh y Eddie son pistas falsas. Explicaron por qué los grupos de funciones no se pueden colocar dentro de un gigante
let rec ... and ...porque afecta cuando las variables de tipo se generalizan. Esto no tiene nada que ver conrecser predeterminado en SML pero no OCaml.fuente
Una razón crucial para el uso explícito de
rectiene que ver con la inferencia de tipo Hindley-Milner, que subyace a todos los lenguajes de programación funcional de tipado estático (aunque cambiado y extendido de varias formas).Si tiene una definición
let f x = x, esperaría que tenga un tipo'a -> 'ay sea aplicable en diferentes'atipos en diferentes puntos. Pero igualmente, si escribelet g x = (x + 1) + ..., esperaríaxser tratado comointen el resto del cuerpo deg.La forma en que la inferencia de Hindley-Milner trata esta distinción es mediante un paso de generalización explícito . En ciertos puntos al procesar su programa, el sistema de tipos se detiene y dice "ok, los tipos de estas definiciones se generalizarán en este punto, de modo que cuando alguien las use, cualquier variable de tipo libre en su tipo será recién instanciada y, por lo tanto, no interferirá con ningún otro uso de esta definición ".
Resulta que el lugar sensato para hacer esta generalización es después de verificar un conjunto de funciones recursivas entre sí. Si lo hace antes, generalizará demasiado, lo que conducirá a situaciones en las que los tipos podrían colisionar. Más adelante, generalizará muy poco, haciendo definiciones que no se pueden usar con instancias de múltiples tipos.
Entonces, dado que el verificador de tipos necesita saber qué conjuntos de definiciones son recursivas entre sí, ¿qué puede hacer? Una posibilidad es simplemente hacer un análisis de dependencia de todas las definiciones en un alcance y reordenarlas en los grupos más pequeños posibles. Haskell en realidad hace esto, pero en lenguajes como F # (y OCaml y SML) que tienen efectos secundarios sin restricciones, esta es una mala idea porque también podría reordenar los efectos secundarios. Entonces, en cambio, le pide al usuario que marque explícitamente qué definiciones son recursivas entre sí y, por lo tanto, por extensión, dónde debería ocurrir la generalización.
fuente
recpero SML no es un contraejemplo obvio. Si la inferencia de tipos fuera el problema por las razones que describe, OCaml y SML no podrían haber elegido soluciones diferentes como lo hicieron. La razón es, por supuesto, que estás hablandoandpara hacer que Haskell sea relevante.Hay dos razones clave por las que esta es una buena idea:
Primero, si habilita las definiciones recursivas, entonces no puede hacer referencia a un enlace anterior de un valor del mismo nombre. Suele ser un modismo útil cuando se hace algo como ampliar un módulo existente.
En segundo lugar, los valores recursivos, y especialmente los conjuntos de valores recursivos entre sí, son mucho más difíciles de razonar, entonces son definiciones que proceden en orden, cada nueva definición se construye sobre lo que ya se ha definido. Al leer dicho código, es bueno tener la garantía de que, a excepción de las definiciones marcadas explícitamente como recursivas, las nuevas definiciones solo pueden referirse a definiciones anteriores.
fuente
Algunas conjeturas:
letno solo se usa para vincular funciones, sino también otros valores regulares. La mayoría de las formas de valores no pueden ser recursivas. Se permiten ciertas formas de valores recursivos (por ejemplo, funciones, expresiones perezosas, etc.), por lo que necesita una sintaxis explícita para indicar esto.letconstrucción es similar a laletconstrucción en Lisp y Scheme; que no son recursivas. Hay unaletrecconstrucción separada en Scheme para recursiva Let'sfuente
let rec xs = 0::ys and ys = 1::xs.Dado este:
Comparar:
Con este:
El primero redefine
fpara aplicar el definido previamentefpara el resultado de aplicargaa. Este último redefinefa un bucle infinito aplicandogaa, que por lo general no es lo que desea en las variantes ML.Dicho esto, es una cuestión de estilo de diseñador de idiomas. Sígueme el rollo.
fuente
Una gran parte de esto es que le da al programador más control sobre la complejidad de sus ámbitos locales. El espectro de
let,let*ylet recofrecen un creciente nivel de potencia y costo.let*ylet recson, en esencia, versiones anidadas de lo simplelet, por lo que usar cualquiera de las dos es más costoso. Esta calificación le permite microgestionar la optimización de su programa, ya que puede elegir qué nivel de permiso necesita para la tarea en cuestión. Si no necesita recursividad o la capacidad de hacer referencia a enlaces anteriores, puede recurrir a un simple let para ahorrar un poco de rendimiento.Es similar a los predicados de igualdad graduados en Scheme. (es decir
eq?,eqv?yequal?)fuente