未来を創る、テックコミュニティー

代表技術通信~Get Programming with Haskell⑦

草場代表
2020/12/01

こんばんは。代表の草場です。

Haskell触ります。「Get Programming with Haskell」についてです。レッスン5の続きです。

5.2.1. 部分的な応用:クロージャを簡単にする

クロージャを作成するためにラムダ関数を使用すると、クロージャを読んだり推論したりするのが本来あるべき姿よりも難しくなります。さらに、これまでに書いてきたクロージャはすべてほぼ同じパターンに従っています。4つの変数を受け取り、それらを追加する関数add4があるとしましょう。

add4 a b c d = a + b + c + d

引数 x を取り、残りの 3 つの引数を待ってクロージャを返す関数 addXto3 を作成します。

addXto3 x = (\b c d ->
add4 x b c d)

明示的なラムダがあるので、何が起こっているのかを推論するのは比較的難しいです。addXYto2を作りたい場合はどうでしょうか?

addXYto2 x y = (\c d ->
add4 x y c d)

4つの引数で視覚的に管理する必要がありますが、この些細な関数でも理解するのは簡単ではありません。ラムダ関数は強力で便利ですが、きちんとした関数定義をしていないと、確実にごちゃごちゃになってしまいます。

Haskellにはこの問題を解決する機能があります。Add4と1つの引数を使うことで、GHCiでミステリー値を定義することができます。

GHCi> mystery = add4 3

このコードを実行すると、エラーが発生しないことがわかります。

GHCi> mystery 2 3 4
12
GHCi>mystery 5 6 7
21

この関数は、あなたがそれに渡す残りの3つの引数に3を追加します。

Haskellで必要なパラメータ数よりも少ない数の関数を呼び出すと、残りのパラメータを待っている新しい関数が出てきます。この言語機能は部分適用と呼ばれています。この関数は、addXto3を書いて、それに引数3を渡したのと同じです。部分適用によってラムダ関数を使わずに済んだだけでなく、addXto3という厄介な名前の関数を定義する必要すらありません! また、addXYto2の動作を簡単に再現することもできます。

GHCi> anotherMystery = add4 2 3
GHCi> anotherMystery 1 2
8
GHCi> anotherMystery 4 5
14

部分的なアプリケーションのおかげで、Haskellでクロージャについて明示的に書いたり考えたりする必要はほとんどありません。genHostRequestBuilder と genApiRequestBuilder にすべての作業は組み込まれており、必要のない引数を省略することで置き換えることができます。

exampleUrlBuilder = getRequestUrl "http://example.com"
myExampleUrlBuilder = exampleUrlBuilder "1337hAsk3ll"

Haskellでは、ラムダ関数を使用してクロージャを作成したい場合もありますが、部分適用を使用する方がはるかに一般的です。

5.3. すべてを一緒にする

部分適用は、引数を最も一般的なものから最も一般的でないものへと順番に並べるというルールを作った理由でもあります。部分適用を使用すると、引数は最初から最後まで適用されます。レッスン 4で addressLetter 関数を定義したときに、このルールに違反しました。

addressLetter name location = locationFunction name
where locationFunction = getLocationFunction location

addressLetterでは、名前の引数は場所の引数の前に来ます。関数を書き換えるのではなく、次のように部分的なアプリケーションに適したバージョンを作ることでこれを修正することができます。

addressLetterV2 location name = addressLetter name location

addressLetter関数を修正したいという一度きりのケースには、これは立派な解決策です。多くのライブラリ関数が2つの引数の場合にこのような同じエラーが発生するコードベースを継承したとしたらどうでしょうか?それぞれのケースを個別に書き出すのではなく、この問題の一般的な解決策を見つけられるといいですね。これまでに学んだことをすべて組み合わせて、簡単な関数でこれを行うことができます。flipBinaryArgs と呼ばれる関数を作成して、関数を受け取り、その引数の順番を反転させ、それ以外の場合はそのまま返します。そのためには、ラムダ関数、ファーストクラス関数、クロージャが必要です。

これらをすべてHaskellの一行にまとめることができます。これで、flipBinaryArgsを使ってaddressLetterV2を書き換え、addressLetterNYを作成することができます。

addressLetterV2 = flipBinaryArgs addressLetter
addressLetterNY = addressLetterV2 "ny"

GHCi> addressLetterNY ("Bob","Smith")
Bob Smith: PO Box 789 - New York, NY, 10013

flipBinaryArgs 関数は、一般化ガイドラインに従っていないコードを修正するだけではありません。多くのバイナリ関数は、除算などの自然な順序を持っています。Haskell の便利なトリックは、+, /, -, * などの接頭辞演算子を括弧で囲むことで、接頭辞関数として使用することができることです。

GHCi> 2 + 3
5
GHCi> (+) 2 3
5
GHCi> 10 / 2
5.0
GHCi> (/) 10 2
5.0

除算と減算では、引数の順序が重要です。

引数には自然な順序があるにもかかわらず、2 番目の引数の周りにクロージャを作成したい場合があることは容易に理解できます。このような場合は flipBinaryArgs を使用します。flipBinaryArgs は便利な関数なので、同じように動作する flip という名前の関数が存在します。

サマリー

関数型プログラミングにおけるクロージャの重要な考え方を学びました。

ラムダ関数、ファーストクラス関数、クロージャがあれば、関数型プログラミングを行うのに必要なものはすべて揃っています。クロージャは、ラムダ関数とファーストクラス関数を組み合わせて、驚くほどの力を発揮します。クローサを使えば、その場で簡単に新しい関数を作成することができます。また、パーシャルアプリケーションを使うことで、クロージャを使った作業がいかに簡単になるかを学びました。部分適用の使用に慣れてしまうと、クロージャを使っていることを全く忘れてしまうこともあるかもしれません。

 

 

この記事を書いた人
草場代表
エディター