Haskell の IO について根本的な誤解があると思います。特に、あなたはこう言います。
'IO String' を [Char] に変換できる関数はないでしょうか?
いいえ、1はありません。そのような関数がないという事実は、Haskell について最も重要なことの 1 つです。
Haskell は非常に原理主義的な言語です。「純粋な」関数 (副作用がなく、同じ入力を与えると常に同じ結果を返す) と「不純な」関数 (ファイルからの読み取り、印刷などの副作用がある) の区別を維持しようとします。画面への書き込み、ディスクへの書き込みなど)。ルールは次のとおりです。
- 純粋関数はどこでも使用できます (他の純粋関数、または非純粋関数内)。
- 不純な関数は、他の不純な関数内でのみ使用できます。
コードがピュアまたはインピュアとしてマークされる方法は、型システムを使用することです。次のような関数シグネチャが表示された場合
digitToInt :: String -> Int
この関数が純粋であることがわかります。a を与えるとString
an が返され、Int
さらに同じを与えると常に同じものが返されInt
String
ます。一方、次のような関数シグネチャ
getLine :: IO String
の戻り値の型がでマークされているため、は不純です。明らかに(ユーザー入力の行を読み取る) は、常に同じ を返すとは限りません。これは、ユーザーが入力した内容に依存するためです。この関数を純粋なコードで使用することはできません。コード。一度行ったら二度と戻れません。String
IO
getLine
String
IO
IO
ラッパーと考えることができます。たとえば のような特定の型が表示された場合、 "は、実行時に任意の I/O を実行し、型の何かを返すアクションであるx :: IO String
ことを意味すると解釈する必要があります" (Haskell では、とはまったく同じであることに注意してください)もの)。x
String
String
[Char]
IO
では、アクションから値にアクセスするにはどうすればよいでしょうか? 幸いなことに、関数の型main
はIO ()
(何らかの I/O を実行して を返すアクションであり()
、何も返さないのと同じです) です。そのため、常にIO
内部で関数を使用できますmain
。Haskell プログラムを実行すると、main
関数が実行されます。これにより、プログラム定義のすべての I/O が実際に実行されます。たとえば、ファイルから読み書きしたり、ユーザーに入力を求めたり、 stdout などへ
次のような Haskell プログラムの構造を考えることができます。
- I/O を行うすべてのコードがタグを取得します (基本的に、ブロック
IO
に入れます)。do
- I/O を実行する必要のないコードは、
do
ブロック内にある必要はありません。これらは「純粋な」関数です。
- 関数
main
は、定義した I/O アクションを、プログラムが実行したいことを実行させる順序で並べます (任意の場所に純粋な関数を散在させます)。
- を実行する
main
と、これらすべての I/O アクションが実行されます。
では、これらすべてを考慮して、どのようにプログラムを作成しますか? さて、機能
readFile :: FilePath -> IO String
ファイルを として読み取りますString
。したがって、それを使用してファイルの内容を取得できます。関数
lines:: String -> [String]
String
aを改行で分割するのでString
、それぞれがファイルの 1 行に対応する s のリストが得られます。関数
init :: [a] -> [a]
.
リストから最後の要素を削除します (これにより、各行の最後の要素が削除されます) 。関数
read :: (Read a) => String -> a
は aを取り、それをorString
などの任意の Haskell データ型に変換します。これらの関数を適切に組み合わせることで、プログラムが完成します。Int
Bool
実際に I/O を実行する必要があるのは、ファイルを読み取るときだけであることに注意してください。したがって、IO
タグを使用する必要があるのはプログラムの唯一の部分です。プログラムの残りの部分は「純粋に」書くことができます。
あなたが必要としているのは記事The IO Monad For People Who Simply Don't Careで、あなたの質問の多くを説明しているようです。「モナド」という言葉を恐れないでください - Haskell プログラムを書くのにモナドとは何かを理解する必要はありません (私の回答で「モナド」という言葉を使っているのはこの段落だけであることに注意してください。今までに4回使用しました...)
これがあなたが書きたい(と思う)プログラムです
run :: IO (Int, Int, [(Int,Int,Int)])
run = do
contents <- readFile "text.txt" -- use '<-' here so that 'contents' is a String
let [a,b,c] = lines contents -- split on newlines
let firstLine = read (init a) -- 'init' drops the trailing period
let secondLine = read (init b)
let thirdLine = read (init c) -- this reads a list of Int-tuples
return (firstLine, secondLine, thirdLine)
の出力へのnpfedwards
適用に関するコメントに答えるには、それが を与えることを理解する必要があります。それを変数にバインドする ( を使用) 場合にのみ、基になる にアクセスできるため、それに適用できます。lines
readFile text.txt
readFile text.txt
IO String
contents <-
String
lines
覚えておいてください:一度行くIO
と二度と戻れません。
1unsafePerformIO
名前が暗示するように、非常に安全ではないため、意図的に無視しています! 自分が何をしているのか本当にわからない限り、絶対に使用しないでください。