関数型プログラミング言語で状態を保存できない場合、ユーザーからの入力の読み取り(つまり、「保存」)や、データの保存など、単純なことをどのように行うのでしょうか。
収集したように、関数型プログラミングには状態がありませんが、データを格納できないという意味ではありません。違いは、私が(Haskell)ステートメントを
let x = func value 3.14 20 "random"
in ...
の値x
は常に同じであることが保証されています...
。何も変更できない可能性があります。同様に、関数f :: String -> Integer
(文字列を取得して整数を返す関数)があるf
場合、その引数を変更したり、グローバル変数を変更したり、ファイルにデータを書き込んだりしないことを確認できます。上記のコメントでsepp2kが述べたように、この非可変性はプログラムについて推論するのに非常に役立ちます。データを折りたたみ、スピンドル化し、切断する関数を記述し、新しいコピーを返すので、それらを一緒にチェーンすることができます。これらの関数呼び出しのうち、「有害」なことは何でも実行できます。あなたはそれx
が常にx
であることを知っています、そして誰かがx := foo bar
宣言の間にどこかに書いたことを心配する必要はありませんx
それは不可能だからです。
ユーザーからの入力を読みたい場合はどうなりますか?KennyTMが言ったように、不純な関数は引数として全世界に渡され、その結果と世界の両方を返す純粋な関数であるという考え方です。もちろん、実際にこれを実行する必要はありません。1つはひどく不格好です。もう1つは、同じワールドオブジェクトを再利用するとどうなるでしょうか。したがって、これはどういうわけか抽象化されます。HaskellはIOタイプでそれを処理します:
main :: IO ()
main = do str <- getLine
let no = fst . head $ reads str :: Integer
...
これmain
は、何も返さないIOアクションであることを示しています。このアクションを実行することが、Haskellプログラムを実行することの意味です。ルールは、IOタイプはIOアクションを決してエスケープできないということです。このコンテキストでは、を使用してそのアクションを紹介しdo
ます。したがって、2つの方法で考えることができるをgetLine
返しIO String
ます。最初に、実行時に文字列を生成するアクションとして。第二に、不純に取得されたためにIOによって「汚染された」文字列として。最初の方がより正確ですが、2番目の方がより役立つ場合があります。<-
とりString
の外IO String
と中に格納しstr
、我々はIOアクションにしているので、我々はそれが「エスケープ」することはできませんので、それは、バックアップをラップする必要があります-ブタを。次の行は、整数(reads
)の読み取りを試み、最初に成功した一致(fst . head
); これはすべて純粋(IOなし)なので、で名前を付けlet no = ...
ます。我々は両方使用することができますno
し、str
中に...
。私たちは、このように(から不純なデータを保存したgetLine
にstr
()、純粋なデータlet no = ...
)。
IOを操作するためのこのメカニズムは非常に強力です。これにより、プログラムの純粋なアルゴリズム部分を、純粋でないユーザーインタラクション側から分離し、型レベルでこれを強制できます。あなたのminimumSpanningTree
関数は、おそらくあなたのコードのどこかで何かを変更したり、ユーザーにメッセージを書き込み、およびようにすることはできません。安全です。
HaskellでIOを使用するために知っておく必要があるのはこれだけです。それでいい場合は、ここで停止できます。しかし、それが機能する理由を理解したい場合は、読み続けてください。(そして、これはHaskellに固有のものであることに注意してください—他の言語は異なる実装を選択するかもしれません。)
そのため、これはおそらくごまかしのようで、どういうわけか純粋なHaskellに不純物を加えていました。しかし、そうではありませんRealWorld
。純粋なHaskell内に完全にIOタイプを実装できることがわかります(が与えられている限り)。アイデアはこれです:IOアクションIO type
は関数と同じでRealWorld -> (type, RealWorld)
、現実の世界を取り、型のオブジェクトtype
と変更されたの両方を返しますRealWorld
。次に、いくつかの関数を定義して、狂わないようにこの型を使用できるようにします。
return :: a -> IO a
return a = \rw -> (a,rw)
(>>=) :: IO a -> (a -> IO b) -> IO b
ioa >>= fn = \rw -> let (a,rw') = ioa rw in fn a rw'
最初の1つreturn 3
は、何もしないIOアクションについて話すことを可能にします3
。これは、現実の世界をクエリせずに単に戻るIOアクションです。>>=
「バインド」と発音オペレータは、私たちはIOアクションを実行することができます。IOアクションから値を抽出し、その値と現実の世界を関数に渡し、結果のIOアクションを返します。>>=
は、IOアクションの結果が決してエスケープされないようにするというルールを適用していることに注意してください。
次に、上記main
を通常の関数アプリケーションのセットに変換できます。
main = getLine >>= \str -> let no = (fst . head $ reads str :: Integer) in ...
Haskellランタイムmain
は最初のからジャンプスタートし、RealWorld
準備が整いました!すべてが純粋で、派手な構文を持っているだけです。
[ 編集: @Conalが指摘するように、これは実際にはHaskellがIOを行うために使用するものではありません。このモデルは、並行性を追加した場合、または実際にIOアクションの最中に世界が変化する方法で破損するため、Haskellがこのモデルを使用することは不可能です。逐次計算の場合にのみ正確です。したがって、HaskellのIOは少し覆い隠されているかもしれません。そうでない場合でも、これほどエレガントではありません。@Conalの観察に従って、厄介な分隊[pdf]のセクション3.1、セクション3.1でSimon Peyton-Jonesが言ったことを確認してください。彼はこれらの線に沿って代替モデルに相当するかもしれないものを提示しますが、それからその複雑さのためにそれを落とし、別の工夫をします。]
繰り返しになりますが、これは、HaskellでIOと可変性が一般的にどのように機能するかを(かなり)説明しています。これがあなたが知りたいすべてであるならば、あなたはここで読むのをやめることができます。理論の最後の1つの線量が必要な場合は、読み続けてください。ただし、現時点では、質問からかなり離れています。
つまり、最後に1つあります。それは、この構造(return
andを使用したパラメトリックタイプ)>>=
が非常に一般的であることを示しています。これはモナドと呼ばれ、とdo
表記されておりreturn
、>>=
それらのどれでも動作します。ここで見たように、モナドは魔法ではありません。魔法のすべては、do
ブロックが関数呼び出しに変わることです。RealWorld
タイプは、私たちがどんな魔法を参照してください唯一の場所です。[]
リストコンストラクタのような型もモナドであり、不純なコードとは何の関係もありません。
モナドの概念については(ほとんど)すべて知っています(満たす必要があるいくつかの法則と正式な数学的定義を除く)。しかし、直感に欠けています。オンラインには途方もない数のモナドチュートリアルがあります。私はこれが好きですが、オプションがあります。ただし、これはおそらく役に立たないでしょう。直感を得るための唯一の実際の方法は、それらを使用し、適切なタイミングでいくつかのチュートリアルを読むことを組み合わせることです。
ただし、IOを理解するのにその直感は必要ありません。モナドを完全に理解することは簡単なことですが、今はIOを使用できます。最初のmain
機能を紹介した後で使用できます。IOコードを不純な言語であるかのように扱うこともできます!しかし、根本的な機能表現があることを忘れないでください。
(PS:長さについて申し訳ありません。私は少し遠くに行きました。)