Haskell GHCi — использование символа EOF на стандартном вводе с помощью getContents

Мне нравится разбирать строки на Python, просто вставляя их в интерпретатор.

>>> s = """Adams, John
... Washington,George
... Lincoln,Abraham
... Jefferson, Thomas
... """
>>> print "\n".join(x.split(",")[1].replace(" ", "")
                    for x in s.strip().split("\n"))
John
George
Abraham
Thomas

Это прекрасно работает с интерпретатором Python, но я хотел бы сделать это с помощью Haskell/GHCi. Проблема в том, что я не могу вставлять многострочные строки. Я могу использовать getContents с символом EOF, но я могу сделать это только один раз, так как символ EOF закрывает стандартный ввод.

Prelude> s <- getContents
Prelude> s
"Adams, John
Adams, John\nWashington,George
Washington,George\nLincoln,Abraham
Lincoln,Abraham\nJefferson, Thomas
Jefferson, Thomas\n^Z
"
Prelude> :{
Prelude| putStr $ unlines $ map ((filter (`notElem` ", "))
Prelude|                         . snd . (break (==','))) $ lines s
Prelude| :}
John
George
Abraham
Thomas
Prelude> x <- getContents
*** Exception: <stdin>: hGetContents: illegal operation (handle is closed)

Есть ли лучший способ сделать это с помощью GHCI? Примечание. Мое понимание getContents (и Haskell IO в целом), вероятно, сильно нарушено.

ОБНОВЛЕНО

Я буду играть с ответами, которые я получил. Вот некоторые вспомогательные функции, которые я сделал (плагиат), которые имитируют цитирование Python """ (заканчивая """, а не начиная) из ответа ephemient.

getLinesWhile :: (String -> Bool) -> IO String
getLinesWhile p = liftM unlines $ takeWhileM p (repeat getLine)

getLines :: IO String
getLines = getLinesWhile (/="\"\"\"")

Чтобы использовать ответ AndrewC в GHCi -

C:\...\code\haskell> ghci HereDoc.hs -XQuasiQuotes
ghci> :{
*HereDoc| let s = [heredoc|
*HereDoc| Adams, John
*HereDoc| Washington,George
*HereDoc| Lincoln,Abraham
*HereDoc| Jefferson, Thomas
*HereDoc| |]
*HereDoc| :}
ghci> putStrLn s
Adams, John
Washington,George
Lincoln,Abraham
Jefferson, Thomas
ghci> :{
*HereDoc| putStr $ unlines $ map ((filter (`notElem` ", "))
*HereDoc|                         . snd . (break (==','))) $ lines s
*HereDoc| :}
John
George
Abraham
Thomas

person pyrospade    schedule 25.08.2012    source источник


Ответы (2)


getContents == hGetContents stdin. К сожалению, hGetContents помечает свой дескриптор как (полу-)закрытый, а это означает, что любая попытка чтения из stdin когда-либо снова завершится ошибкой.

Достаточно ли просто дочитать до пустой строки или другого маркера, никогда не закрывая stdin?

takeWhileM :: Monad m => (a -> Bool) -> [m a] -> m [a]
takeWhileM p (ma : mas) = do
    a <- ma
    if p a
      then liftM (a :) $ takeWhileM p mas
      else return []
takeWhileM _ _ = return []
ghci> liftM unlines $ takeWhileM (not . null) (repeat getLine)
Adams, John
Washington, George
Lincoln, Abraham
Jefferson, Thomas

"Adams, John\nWashington, George\nLincoln, Abraham\nJefferson, Thomas\n"
ghci>
person ephemient    schedule 25.08.2012
comment
Спасибо! Я хотел бы знать, как вручную открыть дескриптор стандартного ввода в Windows. В любом случае, это будет работать на данный момент. Я сделал несколько вспомогательных функций на основе вашего ответа (обновлено в моем вопросе). - person pyrospade; 25.08.2012

Если вы делаете это часто и все равно пишете вспомогательные функции в каком-то модуле, почему бы не пойти на все и не использовать свой редактор и для необработанных данных:

{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
module ParseAdHoc where
import HereDoc
import Data.Char (isSpace)
import Data.List (intercalate,intersperse)  -- other handy helpers

-- ------------------------------------------------------
-- edit this bit every time you do your ad-hoc parsing

adhoc :: String -> String
adhoc = head . splitOn ',' . rmspace

input = [heredoc|
Adams, John
Washington,George
Lincoln,Abraham
Jefferson, Thomas
|]

-- ------------------------------------------------------
-- add other helpers you'll reuse here

main = mapM_ putStrLn.map adhoc.lines $ input

rmspace = filter (not.isSpace)

splitWith :: (a -> Bool) -> [a] -> [[a]]   -- splits using a function that tells you when
splitWith isSplitter list =  case dropWhile isSplitter list of
  [] -> []
  thisbit -> firstchunk : splitWith isSplitter therest
    where (firstchunk, therest) = break isSplitter thisbit

splitOn :: Eq a => a -> [a] -> [[a]]       -- splits on the given item
splitOn c = splitWith (== c)

splitsOn :: Eq a => [a] -> [a] -> [[a]]    -- splits on any of the given items
splitsOn chars = splitWith (`elem` chars)

Было бы проще использовать takeWhile (/=',') вместо head . splitOn ',', но я подумал, что splitOn будет более полезным для вас в будущем.

Здесь используется вспомогательный модуль HereDoc, который позволяет вставлять многострочные строковые литералы в ваш код (например, <<"EOF" в perl или """ в python). Я не могу вспомнить, как я нашел, как это сделать, но я настроил его, чтобы удалить пробелы в первой и последней строках, чтобы я мог начинать и заканчивать свои данные с новой строки.

module HereDoc where
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Data.Char (isSpace)

{-
example1 = [heredoc|Hi.
This is a multi-line string.
It should appear as an ordinary string literal.

Remember you can only use a QuasiQuoter
in a different module, so import this HereDoc module 
into something else and don't forget the
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}|]

example2 = [heredoc|         
This heredoc has no newline characters in it because empty or whitespace-only first and last lines are ignored
                   |]
-}


heredoc = QuasiQuoter {quoteExp = stringE.topAndTail,
                       quotePat = litP . stringL,
                       quoteType = undefined,
                       quoteDec = undefined}

topAndTail = myunlines.tidyend.tidyfront.lines

tidyfront :: [String] -> [String]
tidyfront [] = []
tidyfront (xs:xss) | all isSpace xs = xss
                   | otherwise      = xs:xss

tidyend :: [String] -> [String]
tidyend [] = []
tidyend [xs]     | all isSpace xs = []
                 | otherwise = [xs]
tidyend (xs:xss) = xs:tidyend xss

myunlines :: [String] -> String
myunlines [] = ""
myunlines (l:ls) = l ++ concatMap ('\n':) ls

Вы можете найти Data.Text хорошим источником (вдохновением) вспомогательных функций: http://hackage.haskell.org/packages/archive/text/latest/doc/html/Data-Text.html

person AndrewC    schedule 25.08.2012