Post Page Advertisement [Top]

coolhaskell関数

haskellの関数をおしゃれに把握する



xmonadの設定は、色々なページで色々なxmonad.hsが紹介されています。 しかし、このxmonad.hsの設定書式について、なんとなく直感的に設定出来る部分もあるのですが、haskellを知らないと、意味のわからない部分も多いのではないかと思います。

xmonad.hsは実はhaskellのプログラムそのものです。

haskellは、難解な言語としての評判がありますが、幾つかのコツを掴みさえすれば、他人のxmonad.hsぐらいは読めるようになります。

ここでまず、haskellで一番始めに覚えるべき事は、単語の並びは、関数とその引数であるということです。他のプログラム言語のように関数名の後ろにカッコがあって、その中に引数が入っているという書式が使われていません。haskellの式は、単語が並んでいるだけであり、その先頭が関数名で、それに続くものは全て引数になります。 例えば、他の言語で見られる関数名と引数の形式であるfunc(a,b,c)は、haskellでは、func a b cと表現されます。

haskellの関数を使う


はじめのうちは、引数を表すカッコがないと、どれが引数でどれが関数名か把握しづらいのではないかと感じるかもしれませんが、 単語の並びの先頭が関数名であるという事をしっかり意識してコードをみると、実際にはそんなに混乱しないことに気付くはずです。

ここで、実際にhaskellのコードで試して馴染んでみましょう。ターミナルからghciを起動します。

$ ghci


ここから例として、addという関数を定義して実際に使っていきます。単純に次の通りプロンプトに入れて実行して下さい。

Prelude> add a b = a + b


この式は、2つの引数を取って、それを足した結果を返してくれる関数addを定義しています。

10と15を足すなら

Prelude> add 10 15


という風に書きます。

ではここで、1と2を足した結果に、3と4を足した結果を足す場合はどうなるでしょうか?想像してみて下さい。

Prelude> add (add 1 2) (add 3 4)


式の中では、こんな風にカッコを使って部分的にまとめることが出来ます。まとまった部分の中身の先頭が関数名になっているのが分かるはずです。


結合に強さの強弱がある 

次に、haskellにも演算子記号があります。例えば、+記号です。上の式と同じことは、+演算子記号を混ぜて次のように書けます。

Prelude> add 1 2 + add 3 4


もちろんこれは以下の式と同じとなります。

Prelude> (add 1 2) + (add 3 4)


カッコがあると、そこがひとまとまりであることが強調されて、読みやすいように思えますが、haskell的には、この様な場合、上のカッコがない式のほうが自然です。

私達は普段、四則演算において、加減よりも剰余の結合が強いということを当たり前のように行っています。 これと同様に、haskellでは、演算子記号よりも演算子記号のない部分の結合が強いという規則が決められています。

結合の強弱といってもピンとこないかもしれませんが、 とりあえず、haskellの式では演算子記号があると、 そこで式が区切られるという風に把握してみましょう。 それだけで、haskellの式がぐっと読みやすくなるはずです。

haskellでは、式の表現は、単語の並びになります。 この単語の並びは、日本人としてはピンと来ませんが、英語を話す人達にとって、言語とは配置の言語(英語では場所によってその言葉の役割が決まります。 一方、日本語は場所にかかわらず、助詞で言葉の役割が決まります)であるため、 自然と一番始めにある単語は動詞的な役割であると感じ、これに続く単語はその動詞が働きかける目的物であると感じています。 つまり、動詞部分が関数名であり、引数はその目的物として自然と把握できるのです。 そして、これらに記号が交じると、単語で表現された文の塊と、それを繋いでいる記号と感じることが出来るのかもしれません。

さて次に、haskellのプログラムであるxmoad.hsには、四則演算以外の見知らぬ記号演算子が山のように出てきます。その中でも、よく見る2つの演算子を把握してしまいましょう。

$記号


add関数を使って、1に、3と4を加えた結果を加える式を考えます。

Prelude> add 1 (add 3 4)


add関数の2つ目の引数としてひとまとまりにするためにカッコで囲んでいますが、この様な場合に、$記号を使って、次のように書くことが出来ます。

Prelude> add 1 $ add 3 4


haskellの初学者には「$記号から後ろを最後までカッコで括るのと同じ」とよく説明されますが、まずは、その言葉通りに把握しておけばOKです。

では練習として、次のような式は逆に$記号演算子を使わずカッコで表せば、どういう風にまとまっているのでしょうか?

Prelude> add 1 $ add 3 $ add 3 4


「$記号から後ろを最後までカッコで括るのと同じ」と考えれば、以下の式と同じ意味だとわかります。

Prelude> add 1 (add 3 (add 3 4))


ではここで、はじめにあった次の式に着目します。

Prelude> add (add 1 2) (add 3 4)


この式は、$記号演算子で書き換えるとどうなるのでしょうか?

Prelude> add (add 1 2) $ add 3 4


となります。ここで、次の式のようにはならないことに注意が必要です。

Prelude> add  $ add 1 2 $ add 3 4


なぜ、真ん中の部分は$記号に出来ないかということに対する初学者的な覚え方での理由は「カッコが式の最後まで行っていない」からです。


 .(ピリオド)記号演算子


.(ピリオド)記号演算子は、関数結合をする演算子です。

ここでまず、一つの引数を取る次の2つの関数を定義します。

Prelude> addone a = a + 1
Prelude> threetimes a = a * 3


addone関数は引数に1を足した数、threetimes関数は引数を3倍した数を返す関数です。

この関数を使って、3に1を足して、3倍した数を求める式を考えると次のようになるはずです。

Prelude> threetimes (addone 3)


今度は、5に1を足して、3倍した数を求める式を想像してみて下さい、引数が変わるだけで次のような式になるはずです。

Prelude> threetimes (addone 5)


ここで、もし、ある数にいつでも1を足して3倍した結果を得たい場合、 この「1を足して3倍」する関数に着目して、それを表現すると、この処理の意味がわかりやすくなります。 つまり、関数を「1を足す関数に3倍する関数を合成した関数」として、表現するのです。 そして、この関数を合成する演算子が.(ピリオド)記号です。

上と同じ式を関数合成して書けば以下のようになります。

Prelude> (threetimes . addone) 3


なぜカッコが必要なのかは、今までの流れから理解できると思います。 そして、$演算子を使えば次のようになることも分かるはずです。

Prelude> threetimes . addone $ 3


さて、ここで気がついてほしいのが、 .(ピリオド)記号演算子に渡しているaddoneとthreetimesは関数名であるということです。

プログラム初心者の場合には、一般的に引数として渡されるものは、 数値や文字列など何かを評価した結果としての値であると把握している人が多いと思います。 「関数を引数とし渡すってどういうこと?」と思う方もいるかもしれません。 しかし、実際には、関数そのものも引数とし取り扱うことが出来るプログラム言語はたくさんあります。

そんな中でも、haskellという言語は、関数そのものものを他の値と区別せずに使うことができる代表的な言語であり、とても楽しい言語なのです。

引数の数が足りない関数は関数


先に定義したadd関数を使って、次の式を書いてみましょう

Prelude> add 5 1


答えとして6が得られると思います。

ここで、少し想像して下さい。次の式がある場合、あなたならどう考えますか?

add 5


この様な式は、引数が一つありません。 しかし、あと一つ引数をあたえれば、それと5を足した結果を返してくれるはずです。 ですから、これをいいかえれば、与えられた数に5を足した結果を返す関数であると見ることが出来ます。 そしてなんと、haskellでは、実際にそれを実現しているのです。

実際に次のような定義をしてみましょう。

Prelude> plus_five = add 5


特にエラーなく定義できるはずです。 このplus_fiveは、引数を一つ取り、それに5を加えた結果を返す関数として定義されているのです。 実際に次の式を書いてみましょう。

Prelude> plus_five 1


1に5を加えた結果が得られるはずです。

このように、haskellでは、引数が足りない関数式は、残りの引数をとって、残りの処理をする関数を返すようになっています。 そしてこの様な機能を、部分適用と呼んでいます。

この、部分適用の話を聞いて、今までに見てきた式について振り返ってみると、 実は、既に関数の部分適用や、引数に関数を取る関数に出会っていました。それは、$記号演算子です。

Prelude> add 1 $ add 3 4


$演算子の左は、add関数に、引数が一つしか無い、add関数の部分適用であり、それは何かに1を加える関数を示しています。 つまり、$演算子の第一引数は、引数を一つ取る関数なのです。関数の第二引数は、第一引数の関数の引数となるべき型の何かなのです。 では、更にもう一度、次の式がなぜうまく行かないのか考えてみて下さい。
Prelude> add  $ add 1 2 $ add 3 4


$演算子は、結合規則が最弱なので、そこで式が区切られることになります。 また、$演算子は右結合といって、$演算子が2つ以上ある場合、より右側の演算子から処理がされます。 つまりまず、add 1 2 $ add 3 4が評価されます。 この時、$演算子の右側は7になり、左側は3になります。そして、$演算子を適用しようとすると、第一引数が関数では無いので、エラーとなってしまうのです。 これが、脱初心者した場合の答えになります。

演算子記号と関数


次に、演算子記号について考えます。

Prelude> 1 + 2


通常の計算で見慣れた形を採用していますが、実際には、2つの引数をとる関数です。2つの引数の間に挟まれる形の形式であることから、 中置関数と呼ばれます。 この中置関数である記号演算子も、実は、次のような書き方をすることで、引数の前に関数名が来る書式になります。

Prelude> (+) 1 2


このように、記号演算子を記号をカッコで括ることで、一般の関数と同様に引数の前に関数名として置くことが出来ます。 また、このように引数の前に関数名が来る形式を、前置関数と呼びます。

更に、中置関数である記号演算子をカッコでくくって前置関数にした時、部分適用が可能になります。 この通常の関数並びになると、次のように書けば、通常の前置関数と同様に部分適用が可能になります。

(+) 1


この式は、引数を一つ取って、それに1を加える関数を表しています。

先の.(ピリオド)記号演算子による関数合成では、addone関数とthreetimes関数を定義してそれらを合成しました。 それを部分適用を使って書き換えると次のようになります。

 ((*) 3) . ((+) 1 )


さて、実際にこの関数に引数を与える場合、例えば、5を与える場合は次のようになります。

Prelude>  ((*) 3) . ((+) 1 ) $ 5


実際には、(+) 1という表記は、セクションという(+1)や(1+)という表記が使えます。 そこで、上の式をセクションを使えば、次のように書くことが出来ることになります。

Prelude> (*3) . (+1)  $ 5


いきなり見ると暗号のように見えますが、中置き関数である記号演算子を前置関数化して部分適用したものだと分かれば、 その式の意味を自然と把握できるはずです。

中置関数と結合の変化


最後に、2つの引数をとる普通の関数は、記号演算子のように、ある書式を使うことで、中置関数として書くことが出来きるようになります。

Prelude> add 1 2


ある書式とは、関数名をバックスラッシュで囲うことであり、 この書式を使うと、記号演算子と同じように中置関数として利用することが出来るようになります。

Prelude> 1 `add` 2


中置関数になると、その書式だけでなく、その関数の結合の強さが通常の記号演算子の弱さに変わります。

ですから、関数表記では以下のようなカッコが必要になります。

Prelude> add (add 1 2)  (add 3 4)


しかし、これを中置関数で書くと次のようにカッコが外れます。

Prelude> add 1 2 `add` add 3 4








0 件のコメント:

コメントを投稿

Bottom Ad [Post Page]