こんな煽りがあったので、書いてみることにしました。Haskellが好きなphi16です。この記事はrogy Advent Calendar 15日目の記事です。まぁポエムみたいなものです。
forMは所謂for文に近いですが、最近の言語でよくあるRange-based forですね。つまり1から100までの値でforするっていうことです。その値を受け取る方法が $ \x -> です。for内部の手続きで使われる値としてxをつかうよーっていうことですね。
さてifですけど、なんだか条件がいっぱいあります。1行目に書いた命令によってifの条件文を|に続けていっぱい書けるようになったのです。うれしいね! `mod`は%と同じです、割り算のあまり。条件を満たすときはまた -> を使って続きの手続きを書きます。otherwiseはいままでの条件を満たさない場合用の文ですね、defaultみたいな。
後増えたのはprintですか。putStrLnは文字列を出力する命令で、printは出力できそうなものなら出力してくれる命令です。よくあるかんじ。
うんうんわかってきた。
「$」は実は、「右側全てを括弧で囲んだものとする」という命令です。これがないと手続きがどこで分断されるかわからないのでforMのときは指定する必要がありました。今回は定義なのでだいじょうぶです。
その代わりreturnには$がついています。これは(return i) * iと混乱するかもしれないからつけるのです。勿論括弧で書いてもいいですけど邪魔ですよね。
手続きに値を与えるのはputStrLnと同様にとなりに置けば良いです。そこから返り値を取り出すには <- を使うようです。実行してみると25と、確かにsquareの結果が取り出せていることがわかりますね。
別に他の言語でも手続きは代入できる、って言う人はいるとおもいます。けどそれって「無引数関数」ですよね。それは本当にあなたの言語の「手続き」でしょうか。Objective-Cの^{}が一番表記的には近いかもしれませんね、でも結局それを実行する際にproc()と、関数適用の()が必要になります。
Haskellではそれは必要ありません。
確かに無いですね。これが正しい「第一級手続き」ではないでしょうか?手続きを最も気軽に扱えるのは本当はどんな言語なのでしょうね?
私のせいもあるとおもうんですけど、Haskellが(ローカルな界隈だと)偏見で貶される or 怖がられることが多いので 別にHaskellやってほしいわけじゃないですけど普通の1言語として捉えてほしいなーという気持ちです。私はHaskellを「自由にコードを書ける」「バグを早期発見できる」という点で他の言語よりも優秀な言語だと思っていますけど勿論これは別の話です。
あと全てのプログラマに知っていて欲しいと思うこととして、「自分の言語でつらいことが他の言語で簡単にできることがある」というのがあります。C言語を使わないといけない場合でも、「自作言語のコードからC言語のソースを出力するプログラム」は作れるはずです。Syntax Sugarというのは案外言語の本質で、書きたい記法があるなら書けるようにすればよいのです。これをやりまくったのが現在のAltJS文化です。また自分の知っている言語には無いライブラリが他の言語にあることは多々あります。ライブラリを自分で書くより言語を勉強する方が速いことも多いです。目的を達成する方法はいっぱいあるはずなのです。手続き型が良いのか、関数型が良いのか、それとも・・・ 色々選択肢を持っておくことは大事だとおもいます。正しく言えば「自分には選択肢がある」ということを認識するのが大事だとおもいます。
私はこれからも幸せなコードの書き方を追求していきます。
最近の言語は大体自由にコードを書けるようになっているのでパラダイムの明確な区別ができずにマルチパラダイムと呼んでいる場合が多いみたいですね。Haskellではどれくらい自由にコードを書けるのでしょう。
Haskellでは日常的にdo文を使います。というか使わないとコードは書きにくいのです、確かにそれは純粋関数型の制約なのかもしれません。でもHaskellにはdo文があるのです。この「文法」が何をできるか、というのはまぁ数多くあるモナドの例を眺めてみればわかる通りだとおもいます。
大体do文ってもう見た目が手続き型じゃないですか? Haskellは「作法として」do文を使うことが多いです。これは手続き型なんじゃないですかね。
ちょっと補足するとしたら、実は「1行だけのdo文」は「その文そのもの」と等価です。要は : これで十分です。勿論以降も同じですが、明示的なブロックの導入ということで毎回書いてます。ifで{}を省略できるのと理屈は同じです。
また、とても大切な事実として putStrLnの戻り値は、よくあるvoid型ではなくIO voidです。このIOがとても大事なのです : 副作用の存在を表す型です。
色々と世界が高度になりすぎたお陰で言語拡張が無いと困るようなケースが結構あるのです。でもどうせGHCしか使われないのでぽんぽん突っ込んでもOKみたいな感じ。言語仕様は実質GHCそのものみたいなところがある。
これは事実としてただの関数適用演算子です。左辺に右辺を適用する。ただ、優先順位が最低なのでa + b $ c + dとかやると(a+b) $ (c+d)になります。仕組みがわかる気がする。
さらに右結合なのでf $ g $ xとか書くとf $ (g x)、つまりf (g x)です。便利な気がするじゃないですか。
最近は逆向きの演算子として&が良く使われるようになってしまった(Lensのせい)のでx & g & fとかも増えましたね。まぁ演算子文化が賛否両論なのはめちゃくちゃわかるので何も言わないですけど、Haskellには強い検索ツールがありますし、ドキュメントちゃんとしてるし、型あるし、まぁ、ゆるしてほしい。
表示できないタイプだと実行時エラーになる言語とか、フォーマットエラーをガン無視する言語とは比べ物にならないですね。
重要な差は、「do文の中で羅列された式は順番に()されていく」という規則なのです。要はdo文の中に書いたら勝手に()されるので、わざわざ引数をとるように記述する必要がないということです。そういうように設計されているのです。
これは手続き型言語、正確に言うと純粋世界と副作用を区別できない言語ではできないことだとおもいます(まぁ一貫性を無視すればできるんですよね・・・UFCSとかの・・・)。
まぁ全体を通して言いたいこととしては「do文はすごい」っていう話です、うん。
つまりSyntax Sugarがあるだけで純粋関数型言語でも手続き型のようにコードを書けるということ。逆にいえばそういうSyntax Sugarが無いからC/C++はつらいんです。
ちなみに最近は多くの言語にこのdo文と本質的に等価な機能が導入されています : Generator。いっつもGeneratorの宣伝していますけど、これは本当にdo文みたいなものなので好きなパラダイムをGenerator上で展開できます。うれしい!Syntax Sugarとして提供されている場合と言語構造として定義されている場合があるようですけど、前者で十分とは言え大した差はないですね。
みんなもGenerator使って幸せなプログラムを書いていこう。
Haskellとは
純粋関数型言語、と良く言われます。いろんなプログラミング言語の中でも結構特殊で、例えば変数が無いとか、間違ったコードはすぐコンパイルエラーになるとかあります。でもHaskellは実はマルチパラダイムみたいなところがあるので手続き型のコードは普通に書けます。Hello, World!
それはそうって感じ。「=」が気になるかもしれませんけどdoが{}みたいな意味なのでそんなものかなと。関数呼び出しに括弧が無いのはなんだかメッセージパッシングっぽいですね。まぁ括弧なんて要らないよね、慣習でついてるだけで。FizzBuzz
ちょっと慣れない感じですね?1行目は特殊な便利構文をつかうよーっていう宣言です。GHC(コンパイラ)が許してくれればOKなんです。forMは所謂for文に近いですが、最近の言語でよくあるRange-based forですね。つまり1から100までの値でforするっていうことです。その値を受け取る方法が $ \x -> です。for内部の手続きで使われる値としてxをつかうよーっていうことですね。
さてifですけど、なんだか条件がいっぱいあります。1行目に書いた命令によってifの条件文を|に続けていっぱい書けるようになったのです。うれしいね! `mod`は%と同じです、割り算のあまり。条件を満たすときはまた -> を使って続きの手続きを書きます。otherwiseはいままでの条件を満たさない場合用の文ですね、defaultみたいな。
後増えたのはprintですか。putStrLnは文字列を出力する命令で、printは出力できそうなものなら出力してくれる命令です。よくあるかんじ。
うんうんわかってきた。
入力と出力
大体の言語での手続きは引数と返り値をとれるようです。Haskellでもやってみましょう。 先程forMで使った $ \i -> の中でも \i -> の部分が再登場しています。要はこれは「引数を持つ手続き」をつくる機能なのです。「$」は実は、「右側全てを括弧で囲んだものとする」という命令です。これがないと手続きがどこで分断されるかわからないのでforMのときは指定する必要がありました。今回は定義なのでだいじょうぶです。
その代わりreturnには$がついています。これは(return i) * iと混乱するかもしれないからつけるのです。勿論括弧で書いてもいいですけど邪魔ですよね。
手続きに値を与えるのはputStrLnと同様にとなりに置けば良いです。そこから返り値を取り出すには <- を使うようです。実行してみると25と、確かにsquareの結果が取り出せていることがわかりますね。
手続きとは?
とりあえず今のところ、「do」が手続きを表していることがわかったとおもいます。いろんな言語で{}で手続きを表していたことと似たような感じです。ですがHaskellには他の言語にはない面白い性質があります : 手続きは代入できます。第一級関数ならぬ第一級手続きってところですね。今まで「=」だったのはそういう理由でした。別に他の言語でも手続きは代入できる、って言う人はいるとおもいます。けどそれって「無引数関数」ですよね。それは本当にあなたの言語の「手続き」でしょうか。Objective-Cの^{}が一番表記的には近いかもしれませんね、でも結局それを実行する際にproc()と、関数適用の()が必要になります。
Haskellではそれは必要ありません。
確かに無いですね。これが正しい「第一級手続き」ではないでしょうか?手続きを最も気軽に扱えるのは本当はどんな言語なのでしょうね?
おわりと余談
いやまぁジョーク記事ですけど・・・。私のせいもあるとおもうんですけど、Haskellが(ローカルな界隈だと)偏見で貶される or 怖がられることが多いので 別にHaskellやってほしいわけじゃないですけど普通の1言語として捉えてほしいなーという気持ちです。私はHaskellを「自由にコードを書ける」「バグを早期発見できる」という点で他の言語よりも優秀な言語だと思っていますけど勿論これは別の話です。
あと全てのプログラマに知っていて欲しいと思うこととして、「自分の言語でつらいことが他の言語で簡単にできることがある」というのがあります。C言語を使わないといけない場合でも、「自作言語のコードからC言語のソースを出力するプログラム」は作れるはずです。Syntax Sugarというのは案外言語の本質で、書きたい記法があるなら書けるようにすればよいのです。これをやりまくったのが現在のAltJS文化です。また自分の知っている言語には無いライブラリが他の言語にあることは多々あります。ライブラリを自分で書くより言語を勉強する方が速いことも多いです。目的を達成する方法はいっぱいあるはずなのです。手続き型が良いのか、関数型が良いのか、それとも・・・ 色々選択肢を持っておくことは大事だとおもいます。正しく言えば「自分には選択肢がある」ということを認識するのが大事だとおもいます。
私はこれからも幸せなコードの書き方を追求していきます。
以降、無駄な補足と解説
さすがにまるなげはだめかなっておもって。間違ったコードはコンパイルエラー
間違ってることを勿論100%検出できるはずがありません。でも大体の言語で発生する実行時エラーは、基本的にはコンパイル時の「型チェック」までで検出できます。ついでにいえばHaskellで起きるエラーは大体型エラーです。型ってすごい。実はマルチパラダイム
パラダイムってなんですかね。ある意味では「プログラムの読み書き作法」かな?と思います。確かにHaskellは関数型言語でCは手続き型だとおもいます。では「Haskelは手続き型ではない」のでしょうか?最近の言語は大体自由にコードを書けるようになっているのでパラダイムの明確な区別ができずにマルチパラダイムと呼んでいる場合が多いみたいですね。Haskellではどれくらい自由にコードを書けるのでしょう。
Haskellでは日常的にdo文を使います。というか使わないとコードは書きにくいのです、確かにそれは純粋関数型の制約なのかもしれません。でもHaskellにはdo文があるのです。この「文法」が何をできるか、というのはまぁ数多くあるモナドの例を眺めてみればわかる通りだとおもいます。
大体do文ってもう見た目が手続き型じゃないですか? Haskellは「作法として」do文を使うことが多いです。これは手続き型なんじゃないですかね。
Hello, World!
大体の言語のHello, World!は実は中身ヤバいっていう話がよくあるように(ref:PHP による hello world 入門)、HaskellのHello, World!の全貌を正しく理解するのは勿論困難です。でもどんな言語でもそんなもんですよね。ちょっと補足するとしたら、実は「1行だけのdo文」は「その文そのもの」と等価です。要は : これで十分です。勿論以降も同じですが、明示的なブロックの導入ということで毎回書いてます。ifで{}を省略できるのと理屈は同じです。
また、とても大切な事実として putStrLnの戻り値は、よくあるvoid型ではなくIO voidです。このIOがとても大事なのです : 副作用の存在を表す型です。
括弧なんて要らない
これができるのは「2引数関数」と「1引数を取って1引数関数を返す関数」を区別しないことによるものです。別に他の言語でもf(x)(y)はできるとおもうんですけどちょっと気持ち悪いですよね。Haskellだと括弧が無いのでこれに特に抵抗もなく、まぁ文化なのかなぁっていう感じです。実際いろいろ便利なんですよね。特殊な便利構文
Haskellの言語仕様はきっちり定められていてHaskell2010という名前がついていますが、GHCはそれ以外にも拡張をいっぱい定義しています。使うときはコマンドからとかではなくソースに直接記述できるので他の言語よりも気楽に書けるわけです。色々と世界が高度になりすぎたお陰で言語拡張が無いと困るようなケースが結構あるのです。でもどうせGHCしか使われないのでぽんぽん突っ込んでもOKみたいな感じ。言語仕様は実質GHCそのものみたいなところがある。
$演算子
見た目がわかりにくいんですよね・・・これ無い方が多分初学者には優しいんだとおもいます。これは事実としてただの関数適用演算子です。左辺に右辺を適用する。ただ、優先順位が最低なのでa + b $ c + dとかやると(a+b) $ (c+d)になります。仕組みがわかる気がする。
さらに右結合なのでf $ g $ xとか書くとf $ (g x)、つまりf (g x)です。便利な気がするじゃないですか。
最近は逆向きの演算子として&が良く使われるようになってしまった(Lensのせい)のでx & g & fとかも増えましたね。まぁ演算子文化が賛否両論なのはめちゃくちゃわかるので何も言わないですけど、Haskellには強い検索ツールがありますし、ドキュメントちゃんとしてるし、型あるし、まぁ、ゆるしてほしい。
出力できそう
なんかObjectにtoStringを生やしている言語とか、<<があるならOKみたいな言語とか、いろいろありますけど。HaskellはShow型クラスのインスタンスを定義しておくと「出力できそう」なことになります。まぁ明確に定義されてるよってこと。表示できないタイプだと実行時エラーになる言語とか、フォーマットエラーをガン無視する言語とは比べ物にならないですね。
無引数関数と手続き
まぁHaskellには無引数関数はないんですけど・・・ 一般的な言語で「無引数関数」と「手続き」を区別する必要がある理由は、「引数を取らないと手続きの中身を実行してしまうから」だとおもいます。Haskellでは適当に値を作ったとしてもそれが副作用付きだったら評価しません。遅延評価によるものではなく、「副作用付きの値」から値を取るにはdo文に乗せて「<- 」を書く必要があるからです。とはいえ仕組みは無引数関数と全く同じと考えていいとおもいます。重要な差は、「do文の中で羅列された式は順番に()されていく」という規則なのです。要はdo文の中に書いたら勝手に()されるので、わざわざ引数をとるように記述する必要がないということです。そういうように設計されているのです。
これは手続き型言語、正確に言うと純粋世界と副作用を区別できない言語ではできないことだとおもいます(まぁ一貫性を無視すればできるんですよね・・・UFCSとかの・・・)。
まぁ全体を通して言いたいこととしては「do文はすごい」っていう話です、うん。
定義と代入
勿論違うものです。でも今回のコンテキストでは本質的な違いではないです。事実として「変数」に手続きを代入することはできますし、そこから取り出した手続きを実行するのに括弧はいりません。Syntax Sugarは本質
do文はただのSyntax Sugarです。つまりSyntax Sugarがあるだけで純粋関数型言語でも手続き型のようにコードを書けるということ。逆にいえばそういうSyntax Sugarが無いからC/C++はつらいんです。
ちなみに最近は多くの言語にこのdo文と本質的に等価な機能が導入されています : Generator。いっつもGeneratorの宣伝していますけど、これは本当にdo文みたいなものなので好きなパラダイムをGenerator上で展開できます。うれしい!Syntax Sugarとして提供されている場合と言語構造として定義されている場合があるようですけど、前者で十分とは言え大した差はないですね。
みんなもGenerator使って幸せなプログラムを書いていこう。










