2011-03-05 15 views
6

primero perdón por hacer lo típico de '¿dónde empiezo?', Pero estoy totalmente perdido.¿Cómo leer datos de IO en la estructura de datos y luego procesar la estructura de datos?

He estado leyendo el sitio "Aprende haskell para bien" por lo que parece una edad ahora (más o menos medio semestre. Estoy a punto de terminar el capítulo "Entrada y salida", y Todavía no tengo ni idea de cómo escribir un programa de varias líneas.

He visto la declaración do, y que solo puedes usarla para combinar acciones IO en una sola función, pero no puedo ver cómo estoy ' m va a ir acerca de cómo escribir una aplicación realista.

alguien me puede apuntar en la dirección correcta.

soy de un fondo C, y básicamente estoy usando ja skell para uno de mis módulos este semestre en la universidad, quiero comparar C++ contra haskell (en muchos aspectos). Estoy buscando crear una serie de programas de búsqueda y clasificación para poder comentar lo fácil que son en los respectivos idiomas en comparación con su velocidad.

Sin embargo, estoy empezando a perder mi fe en el uso de Haskell, ya que han pasado seis semanas, y todavía no tengo ni idea de cómo escribir una solicitud completa, y los capítulos en el sitio que estoy leyendo parecen ser cada vez más y más.

Básicamente necesito crear un objeto básico que se almacenará en la estructura (que sé cómo hacer), más con lo que estoy luchando es, ¿cómo puedo crear un programa que lea datos de algún texto? archivo, y rellena la estructura con esa información en primer lugar, luego pasa a procesarla. Como Haskell parece dividir IO y otras operaciones y no va a dejar a escribir varias líneas en un programa, estoy buscando algo como esto:

main = data <- getContent 
     let allLines = lines data 
     let myStructure = generateStruct allLines 
     sort/search/etc 
     print myStructure 

cómo hago para esto? ¿Algún buen tutorial que me ayude a avanzar con programas realistas?

-A

+0

enlace obligatorio: http://www.haskell.org/tutorial/ por si acaso esto se pasa por alto - "suave" es relativo, por supuesto. –

+0

Estoy aprendiendo un haskell para bien también, solo unos pocos días. – Orbit

+0

Además de tener que usar la notación 'do' o soltar la asignación (' <-') y pasar a usar bind ('>> ='), no puede nombrar algo 'data' ya que es una palabra reservada. –

Respuesta

10

Usted mencionó haber visto do notación, ahora es el momento de aprender a utilizar do. Tenga en cuenta su ejemplo main es un IO, usted debe utilizar hacer la sintaxis o se une:

main = do 
    dat <- getContent 
    let allLines = lines dat 
     myStructure = generateStruct allLines 
     sorted = mySort myStructure 
     searchResult = mySearch myStructure 
    print myStructure 
    print sorted 
    print searchResult 

Así que ahora usted tiene una principal que se stdin, lo convierte en [String] través lines, presumiblemente, lo analiza en una estructura y se ejecuta la clasificación y busca en esa estructura. Observe que el código interesante es puro - mySort, mySearch y generateStruct no necesita ser IO (y no puede estarlo, estando dentro de un enlace de let) por lo que en realidad está usando correctamente códigos puros y efectivos.

Te sugiero que veas cómo funciona bind (>>=) y cómo desglosar la notación en bind. This SO question debería ayudar.

+0

tenga en cuenta que desde el punto de vista académico todo lo que incluye IO en Haskell es puro, el único efecto secundario es la no terminación. Sin embargo, es común decir "puro/efectivo" como sinónimos a "no monádico/monádico". Para que la pureza en el aprendizaje sea aún más difícil para los recién llegados, existe una función llamada 'pure'. – nponeccop

1

Si se trata de una cuestión de la lectura y la escritura de stdin resultado a stdout, sin intervención del usuario intevening más - como su mención getContents sugiere - a continuación, la antigua interact :: (String -> String) -> IO(), o las varias otras versiones, por ejemplo, Data.ByteString.interact :: (ByteString -> ByteString) -> IO() o Data.Text.interact :: (Text -> Text) -> IO() son todo lo que se necesitan.interact es básicamente la función 'hacer una herramienta unix de esta función' - asigna funciones puras del tipo correcto a acciones ejecutables (es decir, valores del tipo IO()). Todos los tutoriales Haskell deben mencionarlo en el tercero o cuarta página, con instrucciones sobre compilación.

Así que si usted escribe

main = interact arthur 

arthur :: String -> String 
arthur = reverse 

y compila con ghc --make -O2 Reverse.hs -o reverse entonces todo lo que hace una tubería con ./reverse se entenderá como una lista de caracteres y emergen invierte. Del mismo modo, sea lo que sea que canalice a

main = interact (unlines . meredith . lines) 

meredith :: [String] -> [String] 
meredith = filter (not.null) 

emergerá con las líneas vacías omitidas. Más interesante,

main = interact (unlines . map show . luther . map read . lines) 

luther :: [Int] -> [Int] 
luther = filter even 

se llevará una corriente de caracteres separados por nuevas líneas, leerlos como Int s, la eliminación de los impares, y obteniéndose la corriente adecuadamente filtrada.

main = interact (unlines . map show . emma . map read . lines) 

emma :: [Int] -> Int 
emma = sum . map square 
    where square x = x * x 

imprimirá la suma de los cuadrados de los números separados por nueva línea.

En estos dos últimos casos, luther y emma la 'estructura de datos' interna es [Int], que es bastante aburrida, y la función aplicada a ella es idiota simple, por supuesto. El punto principal es permitir que una de las formas de interact se ocupe de todos los IO, y así obtener imágenes de 'poblar una estructura' y 'procesarlo' fuera de su cabeza. Para usar interact, necesita usar la composición para hacer que el conjunto ceda algo así como la función String -> String. Pero incluso aquí, como en el primer ejemplo runt arthur:: String -> String está definiendo una función genuina en algo más parecido al sentido matemático. Los valores en los tipos String y ByteString son tan puros como los de Bool o Int.

En casos más complicados de este básicos Tipo interact, su tarea es, por tanto, en primer lugar, a pensar cómo las deseadas puros valores de la función que va a centrarse en se pueden asignar a String valores (en este caso, es sólo show para un Int o unlines . map show para un [Int]). interact sabe qué "hacer" con la cadena. - Y luego para descubrir cómo definir un puro mapeo de Strings o ByteString (que contendrá sus datos 'en bruto') a valores en el tipo o tipos que su función principal toma como argumentos. Aquí solo estaba usando map read . lines dando como resultado un [Int]. Si está trabajando en una estructura de árboles más complicada, digamos que necesita una función de [Int] a MyTree Int. Una función más elaborada para poner en esta posición sería un analizador, por supuesto.

Luego puede ir a la ciudad, en este tipo de caso: realmente no hay ninguna razón para pensar en sí mismo como 'programación', 'poblado' y 'procesamiento' en absoluto. Aquí es donde entran en juego todos los dispositivos geniales de LYAH. Su deber es definir un mapeo dentro de la disciplina de definición específica.En los dos últimos casos, se trata [Int]-[Int] y [Int]-Int, pero aquí es un ejemplo similar derivado de la excellent, still incomplete, tutorial en el super-excellent Vector package donde la estructura numérica inicial uno está tratando con es Vector Int

{-# LANGUAGE BangPatterns #-} 
import qualified Data.ByteString.Lazy.Char8  as L 
import qualified Data.Vector.Unboxed   as U 
import System.Environment 

main = L.interact (L.pack . (++"\n") . show . roman . parse) 
    where 
    parse :: L.ByteString -> U.Vector Int 
    parse bytestr = U.unfoldr step bytestr 
    step !s = case L.readInt s of 
     Nothing  -> Nothing 
     Just (!k, !t) -> Just (k, L.tail t) 

-- now the IO and stringy nonsense is out of the way 
-- so we can calculate properly: 

roman :: U.Vector Int -> Int 
roman = U.sum 

Aquí nuevamente roman es estúpido, cualquier función de un Vector de Ints a un Int, por compleja que sea, puede tomar su lugar. Escribir una mejor roman nunca será una cuestión de "poblar" "programación de varias líneas", "procesamiento", etc., aunque, por supuesto, hablamos de esta manera; solo se trata de definir una función genuina por composición de las funciones en Data.Vector y en otros lugares. El cielo es el límite, échale un vistazo a ese tutorial también.

4

Trataré de comenzar con un ejemplo simplificado. Digamos que esto es lo que queremos hacer:

  1. Abra un archivo que contiene una lista de enteros y devuélvalo.
  2. Ordenar esta lista
  3. también vamos a revertir la lista
  4. imprimir el resultado en la pantalla

También vamos a decir que tenemos estas funciones que podemos utilizar:

getContent :: IO [Int] 
sort :: [Int] -> [Int] 
reverse :: [Int] -> [Int] 
show :: a -> String 
putStrLn :: String -> IO() 
Sólo

así que estamos claros, tendré una palabra sobre estas funciones:

  • getContent: inventé esta función, pero si existiera tal función que sería su firma (puede usar getContent = return [3,7,2,1] para realizar pruebas). Estoy seguro de que ya has visto esa firma antes y al menos entiendo vagamente que, dado que hace IO, su firma no puede ser solo getContent :: [Int].
  • sort: Es una función definida en el módulo Data.List, el uso es simple: sort [3,1,2] vuelve [1,2,3]
  • reverse: también definido en el módulo Data.List: reverse [1,3,2] vuelve [2,3,1]
  • show: no necesita importar nada , simplemente úselo: show 11 devuelve la cadena "11"; show [1,2,3] devuelve la cadena "[1,2,3]", etc.
  • putStrLn: toma una cadena, lo pone en la pantalla y vuelve IO(), ahora de nuevo, ya que hace IO su firma no puede ser sólo putStrLn :: Stiring ->().

Bien, ahora tenemos todo lo que necesitamos para crear nuestro programa, el problema ahora es conectar estas funciones juntas. Vamos a empezar con las funciones de conexión:

getContent :: IO [Int] con sort :: [Int] -> [Int]

Creo que si se consigue esta parte, obtendrá fácilmente el resto también.Entonces, el problema es que dado que getContent devuelve IO [Int] y no solo [Int], no puede simplemente ignorar o deshacerse de la parte IO y meterla en sort. Es decir, esto es lo que no puede hacer para conectar estas funciones:

sort (getRidOfIO getContent)

Aquí es donde la operación >>= :: m a -> (a -> m b) -> m b viene al rescate. Ahora note que m, a y b son variables de tipo así que si sustituimos m para IO, a para [Int] y b para [Int], obtenemos el signagure:

>>= :: IO [Int] -> ([Int] -> IO [Int]) -> IO [Int]

Tener un vistazo de nuevo a los getContent y sort funciones y sus firmas y tratar de pensar cómo encajarán en el >>=. Estoy seguro de que notará que puede usar getContent directamente como el primer argumento para >>=. Hasta ahora lo que hará >>= es tomar el [Int] en getContent y lo coloca en la función provista como segundo argumento. ¿Pero cuál será la función en el segundo argumento? No podemos utilizar el sort :: [Int] -> [Int] directamente, la siguiente mejor cosa que podemos intentar es

\listOfInts -> sort listOfInts

pero que todavía tiene la firma [Int] -> [Int] por lo que no ayuda mucho. Aquí es donde el otro héroe llega a la obra, el

return :: a -> m a.

Una vez más, a y m son variables de tipo, permite sustituir ellos y obtendremos

return :: [Int] -> IO [Int]

por lo que añadir \listOfInts -> sort listOfInts y return vamos a buscar juntos:

\listOfInts -> return $ sort listOfInts :: [Int] -> IO [Int]

Lo cual es exactamente lo que queremos poner como segundo argumento al >>=. Por lo tanto permite finaly conectar getContent y sort utilizando nuestro pegamento juntos:

getContent >>= (\listOfInts -> return $ sort listOfInts)

que es lo mismo que (usando la notación do):

do listOfInts <- getContent 
    return $ sort listOfInts 

No, ese es el final de la mayor parte parte aterradora. Y ahora viene posiblemente uno de los momentos aha, trate de pensar cuál es el tipo de resultado de la conexión que acabamos de inventar. Lo estropearé para usted, ... el tipo de

getContent >>= (\listOfInts -> return $ sort listOfInts) es IO [Int] nuevamente.

Permite resumir: tomamos algo del tipo IO [Int] y algo de tipo [Int] -> [Int], pegados esas dos cosas juntas y conseguimos algo nuevo tipo de IO [Int]!

Ahora seguir adelante y tratar exactamente lo mismo: Tomar el objeto IO [Int] que acabamos de crear y la cola juntos (usando >>= y return) con reverse :: [Int] -> [Int].

Creo que escribí demasiado, pero avíseme si algo no estaba claro o si necesita ayuda con el resto.

Wha que he descrito hasta ahora puede ser algo como esto:

getContent :: IO [Int] 
getContent = return [5,2,1,7] 

main :: IO() 
main = do 
    listOfInts <- getContent 
    return $ sort listOfInts 
    return()     -- This is only to sattisfy the signature of main 
Cuestiones relacionadas