Haskellに問題があります。次のようなテキストファイルがあります。
5.
7.
[(1,2,3),(4,5,6),(7,8,9),(10,11,12)].
最初の2つの数値(上記の2と7)と最後の行からリストを取得する方法はわかりません。各行の終わりにドットがあります。
パーサーを作成しようとしましたが、「readFile」という関数がIO Stringというモナドを返します。その種類の文字列から情報を取得する方法がわかりません。
私は文字の配列で作業することを好みます。たぶん 'IO String'から[Char]に変換できる関数はありますか?
HaskellのIOについて、根本的な誤解があると思います。特に、次のように言います。
たぶん 'IO String'から[Char]に変換できる関数はありますか?
いいえ、ありません1、そしてそのような機能がないという事実はHaskellの最も重要なことの1つです。
Haskellは非常に原理的な言語です。 「純粋な」関数(副作用がなく、同じ入力を与えると常に同じ結果を返す)と「純粋でない」関数(ファイルからの読み取り、印刷などの副作用がある)の区別を維持しようとします画面への書き込み、ディスクへの書き込みなど)。ルールは次のとおりです。
コードを純粋または不純としてマークする方法は、型システムを使用することです。次のような関数シグネチャが表示された場合
digitToInt :: String -> Int
あなたはこの関数が純粋であることを知っています。 String
を指定すると、Int
が返され、さらに同じInt
を指定すると、常に同じString
が返されます。一方、関数シグネチャのような
getLine :: IO String
isimpure、なぜならString
の戻り値の型はIO
でマークされているためです。明らかにgetLine
(ユーザー入力の行を読み取る)は、ユーザーが何を入力したかによって異なるため、常に同じString
を返すとは限りません。純粋なコードでこの関数を使用することはできません。純粋なコード。 IO
にいったん戻ると、二度と戻ることはできません。
IO
はラッパーと考えることができます。 x :: IO String
などの特定のタイプが表示された場合、「x
は、任意のI/Oを実行し、タイプString
の何かを返すアクションです」と解釈する必要があります(Haskellでは、String
および[Char]
はまったく同じものです)。
では、IO
アクションから値にアクセスするにはどうすればよいでしょうか。さいわい、関数main
のタイプはIO ()
です(これは、I/Oを実行し、()
を返すアクションです。これは何も返さないのと同じです)。したがって、IO
内でmain
関数をいつでも使用できます。 Haskellプログラムを実行すると、実行しているのはmain
関数を実行することです。これにより、プログラム定義のすべてのI/Oが実際に実行されます。たとえば、ファイルの読み取りと書き込みを行ったり、ユーザーに入力を要求したりできます。 stdoutなどに書き込む.
次のようなHaskellプログラムの構造を考えることができます。
IO
タグを取得します(基本的には、do
ブロックに入れます)do
ブロック内にある必要はありません。これらは「純粋な」関数です。main
関数は、定義したI/Oアクションを、プログラムに実行させたい順序で実行させます(好きなところに純粋な関数が点在しています)。main
を実行すると、それらすべてのI/Oアクションが実行されます。では、これらすべてを踏まえて、プログラムをどのように作成しますか?さて、機能
readFile :: FilePath -> IO String
ファイルをString
として読み取ります。したがって、これを使用してファイルの内容を取得できます。関数
lines:: String -> [String]
String
を改行で分割します。これで、String
sのリストができ、それぞれがファイルの1行に対応します。関数
init :: [a] -> [a]
リストから最後の要素を削除します(これにより、各行の最後の.
が削除されます)。関数
read :: (Read a) => String -> a
String
を受け取り、それをInt
やBool
などの任意のHaskellデータ型に変換します。これらの関数を賢く組み合わせると、プログラムが得られます。
I/Oを実際に行う必要があるのは、ファイルを読み取るときだけであることに注意してください。したがって、それはIO
タグを使用する必要があるプログラムの唯一の部分です。プログラムの残りの部分は「純粋に」書くことができます。
あなたが必要としているのは記事 The IO単に気にしない人のためのモナド であり、多くの質問を説明するはずです。 「モナド」という用語に怖い-あなたはHaskellプログラムを書くためにモナドが何であるかを理解する必要はありません(この段落は「モナド」という単語を使用する私の答えの中で唯一のものであることに注意してください。今は...)
これがあなたが書きたいプログラムだと思います
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)
readFile text.txt
の出力にnpfedwards
を適用することについてのlines
コメントに回答するには、readFile text.txt
がIO String
を提供することを理解する必要があります。これは、contents <-
を使用して変数にバインドした場合にのみ、基礎となるString
にアクセスできるため、 lines
をそれに適用できます。
覚えておいてください。IO
にいったん戻ると、決して戻ることはありません。
1 名前からわかるように、unsafePerformIO
は意図的に無視しています。あなたが本当にあなたが何をしているのかを知っているのでない限り、決してそれを使わないでください。
プログラミング初心者として、私もIO
sに戸惑いました。 IO
に行っても出てこないことを覚えておいてください。クリスは 理由についての素晴らしい説明 を書きました。モナドでIO String
を使用する方法の例をいくつか示すと役立つと思いました。ユーザー入力を読み取り、IO String
を返す getLine を使用します。
line <- getLine
これは、getLine
からのユーザー入力をline
という名前の値にバインドするだけです。これをghciに入力し、:type line
と入力すると、次のように返されます。
:type line
line :: String
ちょっと待って! getLine
はIO String
を返します
:type getLine
getLine :: IO String
では、IO
のgetLine
nessはどうなったのでしょうか。 <-
は何が起こったかです。 <-
はIO
の友達です。モナド内のIO
によって汚染された値を引き出し、通常の関数で使用することができます。モナドはdo
で始まるため、簡単に識別できます。そのようです:
main = do
putStrLn "How much do you love Haskell?"
amount <- getLine
putStrln ("You love Haskell this much: " ++ amount)
私のような人なら、liftIO
がモナドの次の親友であり、$
は書く必要のある括弧の数を減らすのに役立ちます。
では、どのようにしてreadFile
から情報を取得しますか? readFile
の出力がIO String
の場合、次のようになります。
:type readFile
readFile :: FilePath -> IO String
次に、必要なのはフレンドリーな<-
だけです。
yourdata <- readFile "samplefile.txt"
これをghciに入力してyourdata
のタイプを確認すると、単純なString
であることがわかります。
:type yourdata
text :: String
人々がすでに言っているように、2つの関数がある場合、1つは_readStringFromFile :: FilePath -> IO String
_でもう1つは_doTheRightThingWithString :: String -> Something
_である場合、IO
から文字列をエスケープする必要はありません。この2つの機能をさまざまな方法で組み合わせます。
fmap
のIO
の場合(IO
はFunctor
):
_fmap doTheRightThingWithString readStringFromFile
_
_(<$>)
_ for IO
(IO
is Applicative
and _(<$>) == fmap
_)の場合:
_import Control.Applicative
...
doTheRightThingWithString <$> readStringFromFile
_
liftM
for IO
(_liftM == fmap
_)の場合:
_import Control.Monad
...
liftM doTheRightThingWithString readStringFromFile
_
_(>>=)
_ for IO
(IO
is Monad
、fmap == (<$>) == liftM == \f m -> m >>= return . f
)の場合:
_readStringFromFile >>= \string -> return (doTheRightThingWithString string)
readStringFromFile >>= \string -> return $ doTheRightThingWithString string
readStringFromFile >>= return . doTheRightThingWithString
return . doTheRightThingWithString =<< readStringFromFile
_
do
表記:
_do
...
string <- readStringFromFile
-- ^ you escape String from IO but only inside this do-block
let result = doTheRightThingWithString string
...
return result
_
_IO Something
_を取得するたびに。
なぜあなたはそれをそのようにしたいのですか?これで、pureとreferencelyly transparentあなたの言語のプログラム(関数)。これは、タイプがIOフリーであるすべての関数がpureおよび参照的に透過的であることを意味します、同じ引数に対して同じ値を返します。たとえば、doTheRightThingWithString
は、同じSomething
に対して同じString
を返します。ただし、IOフリーではないreadStringFromFile
は毎回異なる文字列を返す可能性があるため(ファイルが変更される可能性があるため)、IO
からこのような純粋でない値をエスケープすることはできません。
このタイプのパーサーがある場合:
myParser :: String -> Foo
そしてあなたは使用してファイルを読みます
readFile "thisfile.txt"
次に、ファイルを読み取って解析できます
fmap myParser (readFile "thisfile.txt")
その結果はタイプIO Foo
になります。
fmap
は、myParser
がIOの「内部」で実行されることを意味します。
もう1つの考え方は、myParser :: String -> Foo
、fmap myParser :: IO String -> IO Foo
です。