2012-02-17 20 views
29

Duplicar posible:
Why are functions in Ocaml/F# not recursive by default?¿Por qué ocaml necesita tanto "let" como "let rec"?

OCaml utiliza let para definir una nueva función o let rec para definir una función que es recursivo. ¿Por qué necesita ambos? ¿No podríamos simplemente usar let para todo?

Por ejemplo, para definir una función no recursiva sucesor y factorial recursiva en OCaml (en realidad, en el intérprete OCaml) podría escribir

let succ n = n + 1;; 

let rec fact n = 
    if n = 0 then 1 else n * fact (n-1);; 

Mientras que en Haskell (GHCi) Puedo escribir

let succ n = n + 1 

let fact n = 
    if n == 0 then 1 else n * fact (n-1) 

¿Por qué OCaml distingue entre let y let rec? ¿Es un problema de rendimiento o algo más sutil?

+6

¿Por qué votar para cerrar ..? Parece una pregunta razonable. – Sean

+0

Ver http://stackoverflow.com/questions/900585/why-are-functions-in-ocaml-f-not-recursive-by-default –

+0

@Sean: esta es una super-FAQ. – Ashe

Respuesta

27

Bueno, tener ambos disponibles en lugar de uno solo le da al programador un control más estricto en el alcance. Con let x = e1 in e2, el enlace solo está presente en el entorno e2, mientras que con let rec x = e1 in e2 el enlace está presente en los entornos e1 y e2.

(Edit: Quiero hacer hincapié en que es no un problema de rendimiento, que no hace ninguna diferencia en absoluto.)

Aquí hay dos situaciones en las que tener esta unión no recursivo es útil:

  • sombreando una definición existente con un refinamiento que usa el enlace anterior. Algo como: let f x = (let x = sanitize x in ...), donde sanitize es una función que asegura que la entrada tenga alguna propiedad deseable (por ejemplo, toma la norma de un vector posiblemente no normalizado, etc.). Esto es muy útil en algunos casos.

  • metaprogramación, por ejemplo macro escritura. Imagine que quiero definir una macro SQUARE(foo) que desensanca en let x = foo in x * x, para cualquier expresión foo. Necesito este enlace para evitar la duplicación de código en la salida (no quiero que SQUARE(factorial n) calcule factorial n dos veces). Esto solo es higiénico si el enlace let no es recursivo, de lo contrario no podría escribir let x = 2 in SQUARE(x) y obtener un resultado correcto.

Por lo tanto, afirmo que es muy importante contar con el enlace recursivo y no recursivo disponible. Ahora, el comportamiento predeterminado de del let-binding es una cuestión de convención. Se podría decir que let x = ... es recursivo, y se debe usar let nonrec x = ... para obtener la carpeta no recursiva. Escoger uno por defecto u otro es una cuestión de qué estilo de programación desea favorecer y hay buenas razones para elegir. Haskell sufre¹ de la falta de disponibilidad de este modo no recursivo, y OCaml tiene exactamente el mismo defecto en el nivel de tipo: type foo = ... es recursivo, y no hay una opción no recursiva disponible; consulte this blog post.

¹: cuando Google Code Search estaba disponible, lo usé para buscar en el código de Haskell para el patrón let x' = sanitize x in .... Esta es la solución habitual cuando el enlace no recursivo no está disponible, pero es menos seguro porque te arriesgas a escribir x en lugar de x' por error más adelante; en algunos casos quieres tener ambos disponibles, por lo que elegir un nombre diferente puede ser voluntario. . Una buena expresión sería usar un nombre de variable más larga para el primer x, como unsanitized_x. De todos modos, buscando x' litterally (sin otro nombre de variable) y x1 dieron muchos resultados. Erlang (y todos los lenguajes que intentan dificultar el sombreado variable: Coffeescript, etc.) tienen problemas aún peores de este tipo.

Dicho esto, la opción de tener enlaces Haskell recursivos por defecto (en lugar de no recursivo) ciertamente tiene sentido, ya que es coherente con la evaluación diferida por defecto, lo que hace que sea muy fácil construir valores recursivos, mientras que los lenguajes por defecto tienen más restricciones sobre las definiciones recursivas que tienen sentido.

+2

Bonito post, pero no me siento convencido por la metaprogramación. Se podría argumentar que se debe escribir 'square' como una función y cuando el compilador tenga capacidades de alineación, esto se comportará efectivamente como una macro, con el cambio de nombre alfa y todo eso. – Ingo

+3

Además, el problema de sombreado se puede hacer más fácilmente con modismos como: 'foo x = work (sanitized x) donde trabajo x = ....' – Ingo

+0

@Ingo, la metaprogramación es mucho más que simplemente expandir funciones genéricas. –

Cuestiones relacionadas