Pregunta ¿Por qué a Haskell (a veces) se lo conoce como "el mejor lenguaje imperativo"?


(Espero que esta pregunta sea sobre el tema: intenté buscar una respuesta pero no encontré una respuesta definitiva. Si esto sucede fuera del tema o si ya se respondió, modifíquelo / quítelo).

Recuerdo haber escuchado / leído el comentario medio en broma sobre que Haskell era el mejor lenguaje imperativo algunas veces, lo que por supuesto suena raro ya que Haskell es mejor conocido por su funcional caracteristicas.

Así que mi pregunta es, ¿qué cualidades / características (si las hay) de Haskell dan razones para justificar que Haskell se considere el mejor lenguaje imperativo - ¿o es más una broma?


75
2017-07-08 09:31


origen


Respuestas:


Lo considero una verdad a medias. Haskell tiene una asombrosa capacidad de abstracción, y eso incluye abstracción sobre ideas imperativas. Por ejemplo, Haskell no tiene un bucle while imperativo incorporado, pero podemos simplemente escribirlo y ahora lo hace:

while :: (Monad m) => m Bool -> m () -> m ()
while cond action = do
    c <- cond
    if c 
        then action >> while cond action
        else return ()

Este nivel de abstracción es difícil para muchos lenguajes imperativos. Esto se puede hacer en idiomas imperativos que tienen cierres; p.ej. Python y C #.

Pero Haskell también tiene la capacidad (altamente única) de caracterizar los efectos secundarios permitidosusando las clases de Monad Por ejemplo, si tenemos una función:

foo :: (MonadWriter [String] m) => m Int

Esta puede ser una función "imperativa", pero sabemos que solo puede hacer dos cosas:

  • "Salida" una secuencia de cadenas
  • devolver un int

No puede imprimir en la consola o establecer conexiones de red, etc. Combinado con la capacidad de abstracción, puede escribir funciones que actúen sobre "cualquier cálculo que produzca una secuencia", etc.

En realidad, se trata de las habilidades abstractas de Haskell lo que lo convierte en un lenguaje imperativo muy fino.

Sin embargo, la mitad falsa es la sintaxis. Encuentro a Haskell bastante prolijo e incómodo de usar en un estilo imperativo. Aquí hay un ejemplo de cálculo de estilo imperativo usando lo anterior while loop, que encuentra el último elemento de una lista vinculada:

lastElt :: [a] -> IO a
lastElt [] = fail "Empty list!!"
lastElt xs = do
    lst <- newIORef xs
    ret <- newIORef (head xs)
    while (not . null <$> readIORef lst) $ do
        (x:xs) <- readIORef lst
        writeIORef lst xs
        writeIORef ret x
    readIORef ret

Toda esa basura IORef, la doble lectura, teniendo que unir el resultado de una lectura, fmapping (<$>) para operar en el resultado de un cálculo en línea ... todo es muy complicado. Tiene mucho sentido desde un funcional perspectiva, pero los lenguajes imperativos tienden a barrer la mayoría de estos detalles debajo de la alfombra para que sean más fáciles de usar.

Es cierto, tal vez si usamos un diferente whileel combinador de estilo sería más limpio. Pero si llevas esa filosofía lo suficientemente lejos (usando un rico conjunto de combinadores para expresarte con claridad), entonces vuelves a la programación funcional. Imperativo estilo Haskell simplemente no "fluye" como un lenguaje imperativo bien diseñado, p. pitón.

En conclusión, con un estiramiento facial sintáctico, Haskell podría ser el mejor lenguaje imperativo. Pero, por la naturaleza de los estiramientos faciales, sería reemplazar algo internamente hermoso y real con algo externamente bello y falso.

EDITAR: Contraste lastElt con esta transliteración python:

def last_elt(xs):
    assert xs, "Empty list!!"
    lst = xs
    ret = xs.head
    while lst:
        ret = lst.head
        lst = lst.tail
    return ret 

El mismo número de líneas, pero cada línea tiene bastante menos ruido.


EDIT 2

Por lo que vale, así es como puro reemplazo en Haskell se ve así:

lastElt = return . last

Eso es. O, si me prohibes usar Prelude.last:

lastElt [] = fail "Unsafe lastElt called on empty list"
lastElt [x] = return x
lastElt (_:xs) = lastElt xs

O, si quieres que funcione en cualquier Foldable estructura de datos y reconoce que en realidad no necesitar  IO para manejar errores:

import Data.Foldable (Foldable, foldMap)
import Data.Monoid (Monoid(..), Last(..))

lastElt :: (Foldable t) => t a -> Maybe a
lastElt = getLast . foldMap (Last . Just)

con Map, por ejemplo:

λ➔ let example = fromList [(10, "spam"), (50, "eggs"), (20, "ham")] :: Map Int String
λ➔ lastElt example
Just "eggs"

los (.) operador es composición de la función.


83
2017-07-08 09:59



No es una broma, y ​​lo creo. Trataré de mantener esto accesible para aquellos que no conocen ningún Haskell. Haskell usa la notación do (entre otras cosas) para permitirte escribir un código imperativo (sí, usa mónadas, pero no te preocupes por eso). Estas son algunas de las ventajas que Haskell le ofrece:

  • Fácil creación de subrutinas. Digamos que quiero que una función imprima un valor para stdout y stderr. Puedo escribir lo siguiente, definir la subrutina con una línea corta:

    do let printBoth s = putStrLn s >> hPutStrLn stderr s
       printBoth "Hello"
       -- Some other code
       printBoth "Goodbye"
    
  • Código fácil de pasar alrededor. Dado que he escrito lo anterior, si ahora quiero usar el printBoth función para imprimir toda una lista de cadenas, eso se hace fácilmente pasando mi subrutina al mapM_ función:

    mapM_ printBoth ["Hello", "World!"]
    

    Otro ejemplo, aunque no es imprescindible, es la clasificación. Supongamos que desea ordenar cadenas solo por longitud. Puedes escribir:

    sortBy (\a b -> compare (length a) (length b)) ["aaaa", "b", "cc"]
    

    Lo cual te dará ["b", "cc", "aaaa"]. (Puedes escribirlo más corto que eso, también, pero no importa por ahora).

  • Código fácil de reutilizar. Ese mapM_ función se usa mucho, y reemplaza para cada bucles en otros idiomas. También hay forever que actúa como un tiempo (verdadero) y varias otras funciones que se pueden pasar código y ejecutarlo de diferentes maneras. Por lo tanto, los bucles en otros idiomas se reemplazan por estas funciones de control en Haskell (que no son especiales; puede definirlas usted mismo muy fácilmente). En general, esto dificulta que la condición de bucle sea incorrecta, al igual que para cada bucle es más difícil de obtener que los equivalentes de iterador de larga duración (por ejemplo, en Java) o bucles de indexación de matriz (por ejemplo, en C).

  • Encuadernación no asignación. Básicamente, solo puede asignar una variable una vez (como una única asignación estática). Esto elimina mucha confusión sobre los posibles valores de una variable en cualquier punto dado (su valor solo se establece en una línea).
  • Efectos secundarios contenidos Digamos que quiero leer una línea de stdin y escribirla en stdout luego de aplicarle alguna función (lo llamaremos foo). Puedes escribir:

    do line <- getLine
       putStrLn (foo line)
    

    Sé de inmediato que foo no tiene ningún efecto secundario inesperado (como la actualización de una variable global, o la desasignación de memoria, o lo que sea), porque su tipo debe ser Cadena -> Cadena, lo que significa que es una función pura; cualquier valor que pase, debe devolver el mismo resultado cada vez, sin efectos secundarios. Haskell separa muy bien el código de efectos laterales del código puro. En algo como C, o incluso Java, esto no es obvio (¿ese método getFoo () cambia el estado? Esperaría que no, pero podría hacer ...).

  • Recolección de basura. Hoy en día, muchos idiomas son basura, pero vale la pena mencionar: no hay problemas para asignar y desasignar la memoria.

Probablemente haya algunas otras ventajas además, pero esas son las que vienen a la mente.


22
2017-07-08 10:01



Además de lo que otros ya han mencionado, tener acciones colaterales ser de primera clase a veces es útil. Aquí hay un ejemplo tonto para mostrar la idea:

f = sequence_ (reverse [print 1, print 2, print 3])

Este ejemplo muestra cómo puedes construir cálculos con efectos secundarios (en este ejemplo print) y luego colocar las estructuras de datos o manipularlas de otras maneras, antes de ejecutarlas.


15
2017-07-08 11:20