2011-02-06 7 views
11

[Esta pregunta está motivada por el capítulo 9 en "Real World Haskell"]

He aquí una función simple (corte a lo esencial):

saferOpenFile path = handle (\_ -> return Nothing) $ do 
     h <- openFile path ReadMode 
     return (Just h) 

¿Por qué necesito que $?

Si el segundo argumento para manejar no es un bloque do, no lo necesito. Los siguientes funciona bien:

handle (\_ -> putStrLn "Error calculating result") (print x) 

Cuando traté de quitar la compilación $ fracasaron. Puedo conseguir que funcione de forma explícita si añado parens, es decir

saferOpenFile path = handle (\_ -> return Nothing) (do 
     h <- openFile path ReadMode 
     return (Just h)) 

puedo entender que, pero supongo que estoy esperando que cuando Haskell golpea el do se debe pensar "estoy empezando un bloque", y no deberíamos tener que poner explícitamente el $ allí para romper las cosas.

También pensé que empuja el bloque do a la siguiente línea, así:

saferOpenFile path = handle (\_ -> return Nothing) 
    do 
    h <- openFile path ReadMode 
    return (Just h) 

pero eso no funciona sin parens tampoco. Eso me confunde, porque las siguientes obras:

add a b = a + b 

addSeven b = add 7 
    b 

estoy seguro de que voy a llegar al punto donde lo acepto como "eso es sólo la forma de escribir idiomática Haskell", pero ¿alguien tiene alguna perspectiva para dar ? Gracias por adelantado.

+0

Disculpa, amigos, no sé stackoverflow muy bien. El código es solo basura. (¿Por qué no hay una "Pregunta de vista previa"). Intentaré volver a publicarlo. – Jonathan

+0

simplemente presione el botón "código" en la barra de herramientas del editor (parece un par de llaves). Además, la vista previa está justo debajo de donde escribe, actualizada en tiempo real ... – Jon

+1

¡Ah, NoScript necesitaba permitir un dominio más! Se ve bien ahora. Gracias, Jon! – Jonathan

Respuesta

6

De acuerdo con los Haskell 2010 report, do desugars como este:

do {e;stmts} = e >>= do {stmts}

do {p <- e; stmts} = let ok p = do {stmts} 
         ok _ = fail "..." 
        in e >>= ok 

Para el segundo caso, que es el mismo que escribirlo desugaring como esto (y más fácil para fines demostrativos):

do {p <- e; stmts} = e >>= (\p -> do stmts)

Así que supongamos que escribe esto:

-- to make these examples shorter 
saferOpenFile = f 
o = openFile 

f p = handle (\_ -> return Nothing) do 
    h <- o p ReadMode 
    return (Just h) 

Se desugars a esto:

f p = handle (\_ -> return Nothing) (o p ReadMode) >>= (\h -> return (Just h)) 

Cuál es el mismo que esto:

f p = (handle (\_ -> return Nothing) (o p ReadMode)) >>= (\h -> return (Just h)) 

A pesar de que probablemente destinados a que signifique esto:

handle (\_ -> return Nothing) ((o p ReadMode) >>= (\h -> return (Just h))) 

tl; dr - cuando se desajusta la sintaxis, no se entrelaza toda la afirmación como se creería que debería (a partir de Haskell 2010).

Esta respuesta es solamente yo sepa y

  1. puede no ser correcta
  2. puede haber algún día obsoleta si es correcto

Nota: si usted dice a mí ", pero los usos desugaring reales una declaración 'let' que debería agrupar e >>= ok, ¿verdad? ", entonces respondería lo mismo que la tl; dr para las declaraciones do: no se entrelaza/agrupa como crees que lo hace.

+0

Bueno, supongo que el informe hace que esta respuesta sea definitiva. Creo que me está costando más tiempo acostumbrarme a la aplicación de función de estilo lambda de Haskell que pensé que era. – Jonathan

+0

Es realmente una combinación de esta respuesta y la de Callum que atrae la respuesta completa a su pregunta. Este para explicar por qué '$' es necesario y el otro para explicar lo que hace. –

+2

No es esta respuesta. El desempañamiento siempre funciona en árboles de sintaxis abstractos terminados, lo que significa que toma una entrada 'totalmente entre paréntesis' y produce una salida 'totalmente entre paréntesis'. No cambia la definición de cómo se ve una entrada válida, o cuando necesita paréntesis. (Se define más como macros Lisp que como macros de lector de Lisp o macros de secuencia de token de C). –

10

Esto se debe al orden de operaciones de Haskell. La aplicación de función ata más apretada, lo que puede ser una fuente de confusión. Por ejemplo:

add a b = a + b 
x = add 1 add 2 3 

Haskell interpreta esto como: la función agregar aplicada a 1 y la función agregar. Probablemente, esto no es lo que el programador pretendía. Haskell se quejará de esperar un Num para el segundo argumento, pero obteniendo una función en su lugar.

hay dos soluciones:

1) La expresión puede paréntesis:

x = add 1 (add 2 3) 

Qué Haskell interpretará como: la función Add aplicado a 1, entonces el valor de añadir 2 3.

Pero si la anidación es demasiado profunda, esto puede ser confuso, de ahí la segunda solución.

2) El operador $:

x = add 1 $ add 2 3 

$ es un operador que aplica una función a sus argumentos. Haskell lee esto como: la función (agregar 1) aplicada al valor de sumar 2 3. Recuerde que en Haskell, las funciones se pueden aplicar parcialmente, por lo que (sumar 1) es una función perfectamente válida de un argumento.

El operador $ se puede utilizar varias veces:

x = add 1 $ add 2 $ add 3 4 

Qué solución que elija será determinado por lo que usted piensa que es más fácil de leer en un contexto particular.

+3

Gracias, Callum. Supuse que ($) era sobre todo sintaxis cuando traté de escribir una definición de él y se me ocurrió ($) f x = f x. De mis fuentes limitadas, parece que a Haskellers realmente le gusta evitar los paréntesis, siempre que sea posible. Supongo que es difícil venir de otros idiomas, donde soy bueno leyendo add (1, add (3, 4)). Especialmente cuando se usan combinadores – Jonathan

10

Esto realmente no es muy específico para do -notación. Tampoco puede escribir cosas como print if x then "Yes" else "No" o print let x = 1+1 in x*x.

Puede verificar esto en la definición de la gramática en el comienzo de chapter 3 of the Haskell Report: una expresión do o una condicional o una expresión let es un LEXP, pero el argumento de la aplicación de la función es una AEXP, y un LEXP generalmente no es válido como aexp.

Esto no le dice por qué se hizo esa elección en el Informe, por supuesto. Si tuviera que adivinar, podría suponer que es para extender la regla útil "la aplicación de función se une más apretada que cualquier otra cosa" a la vinculación entre, por ejemplo, do y su bloque.Pero no sé si esa fue la motivación original. (Creo que los formularios rechazados como print if x then ... son difíciles de leer, pero eso puede deberse a que estoy acostumbrado a leer el código que acepta Haskell).

Cuestiones relacionadas