1.最初のステップ
2.機能を組み合わせる
3.部分使用(カリー化)
4.宣言型プログラミング
5. Quintessential表記
6.不変性とオブジェクト
7.不変性と配列
8.レンズ
9.結論
この投稿は、Ramda Style Thinkingと呼ばれる関数型プログラミングに関する一連の記事の第5部です。
第4部では、命令型(これを行う方法を伝える)ではなく、宣言型(コンピューターに何をすべきかを説明する)でコードを記述することについて話しました。
お気付きかもしれませんが、私たちが書いた関数の一部( forever21
、 drivingAge
およびwater
など)はすべてパラメーターを受け取り、新しい関数を作成して、この関数をパラメーターに適用します。
これは関数型プログラミングで非常に一般的なパターンであり、Ramdaはコードをもう少しクリアするユーティリティを提供します。
無意味な表記
Ramdaには2つの基本的なガイドラインがあり、 第3部で説明しました。
1.最後にデータを転送する
2.すべてのものをカレー
これらの2つの原則は、機能プログラマが「無意味」と呼ぶスタイルにつながります。 私はコードレスを「 データ?そしてデータはどこにありますか?データはどこにもありません 」と考えるのが好きです 。
Ramdaを選ぶ理由は1つあります。 これは無意味な表記のスタイルを完全に示しています。 「 データはどこにありますか? 」、「 オーケー、それだけです!データを表示できますか? 」、「 データが必要なだけ です。 」などのタイトルがあります 。
すべての例が完全に無意味になることを保証するために必要なツールはまだありませんが、すでに何かを始めることができます。
forever21
見てみましょう。
const forever21 = age => ifElse(gte(__, 21), always(21), inc)(age)
age
は2回しか発生しないことに注意してください: ifElse
を呼び出して返される関数を使用する場合、引数リストに1回と関数の最後に1回ifElse
ます。
Ramdaを使用する際に注意を払うと、多くの場所でこのパターンに気付くでしょう。 これはほとんどの場合、関数をブラシレススタイルに変換できることを意味します。
それがどのように見えるか見てみましょう:
const forever21 = ifElse(gte(__, 21), always(21), inc)
そして、パフ! age
消したばかりです。 無知な表記法。 関数の2つのバージョン間で動作に違いはないことに注意してください。 このコードは年齢を受け取る関数を返しますが、現在はage
パラメーターを明示的に指定していません。
alwaysDrivingAge
とwater
alwaysDrivingAge
同じことができます。
alwaysDrivingAge
は次のようになりました。
const alwaysDrivingAge = age => ifElse(lt(__, 16), always(16), identity)(age)
無意味にするために、同様の変換を適用できます。
const alwaysDrivingAge = when(lt(__, 16), always(16))
そして、 water
機能を残しました:
const water = temperature => cond([ [equals(0), always('water freezes at 0°C')], [equals(100), always('water boils at 100°C')], [T, temp => `nothing special happens at ${temp}°C`] ])(temperature)
そして、これは無意味な対応物です:
const water = cond([ [equals(0), always('water freezes at 0°C')], [equals(100), always('water boils at 100°C')], [T, temp => `nothing special happens at ${temp}°C`] ])
マルチ引数関数
複数の引数を取る関数についてはどうですか? パート3からtitlesForYear
関数に戻りましょう。
const titlesForYear = curry((year, books) => pipe( filter(publishedInYear(year)), map(book => book.title) )(books) )
books
は2回しか発生しないことに注意してください。1回は引数リストの最後のパラメーターとして(データが最後に来ます!)、1回はパイプラインを使用する関数の最後に1回です。 これは、以前のage
見たパターンに似ているため、この状況に同様の変換を適用しましょう。
const titlesForYear = year => pipe( filter(publishedInYear(year)), map(book => book.title) )
動作します! titlesForYear
無意味なバージョンがtitlesForYear
。
正直なところ、この関数のモックバージョンを使用したくないのは、JavaScriptが一連の単一引数関数を呼び出すことに同意していないためです。これについては、以前の投稿で既に説明しました。
パイプラインでtitlesForYear
を使用する場合、すべてが素晴らしいものになります。 titlesForYear(2012)
と呼ぶことができます。 ただし、この関数を個別に使用する場合は、パターンに戻る必要があります)(
前の投稿で見たように: titlesForYear(2012)(books)
。私の意見では、それは価値がありません。
しかし、いつでも、上記のパターンに続く(またはリファクタリングして)上記のパターンの単一引数関数がある場合、ほとんど常に無意味になります。
ブラシレスリファクタリング
関数がこのパターンに従わない場合があります。 1つの関数で複数回データの操作を開始できます。
第2部から同様の例がいくつかあります。 これらの例では、 both
、 pipe
、 compose
などのboth
を使用して関数を結合するようにコードをリファクタリングcompose
。 これが完了すると、これらの関数を無意味にキャストするのは非常に簡単です。
isEligibleToVote
メソッドを見てみましょう。 これが私たちが始めた場所です:
const wasBornInCountry = person => person.birthCountry === OUR_COUNTRY const wasNaturalized = person => Boolean(person.naturalizationDate) const isOver18 = person => person.age >= 18 const isCitizen = person => wasBornInCountry(person) || wasNaturalized(person) const isEligibleToVote = person => isOver18(person) && isCitizen(person)
isCitizen
から始めましょう。 この関数はperson
を取り、それに2つの異なる関数を適用し、結果を||
と組み合わせます。 。 第2部ですでに学んだように、代わりにeither
を使用して2つの関数を新しい関数に結合し、それをperson
適用できます。
const isCitizen = person => either(wasBornInCountry, wasNaturalized)(person)
both
でisEligibleToVote
を使用して同様のことを実行できます。
const isEligibleToVote = person => both(isOver18, isCitizen)(person)
このリファクタリングが終了したので、両方の関数が先ほど説明したパターンに従っていることがわかります: person
は2回言及されています。 これで、それらを無意味なスタイルに変換できます。
const isCitizen = either(wasBornInCountry, wasNaturalized) const isEligibleToVote = both(isOver18, isCitizen)
なんで?
無意味なスタイルは慣れるのに時間がかかります。 どこにでもないデータ引数に適応するのは難しいかもしれません。 Ramda関数に精通し、通常必要な引数の数を知ることも重要です。
しかし、いつかそれらを学ぶと、それらは必要に応じて非常に強力になり、さまざまな興味深い方法で組み合わされた小さなチューンフリー機能のセットを作成します。
無意味なスタイルにはどのような利点がありますか? これは、関数型プログラミングに別のバッジを与えることが認められている学術的な教訓に過ぎないと主張できます。 それにもかかわらず、私はあなたがそれに慣れるのに時間を費やさなければならないとしても、彼にはまだいくつかの本当の利点があると思います:
- これにより、プログラムがよりシンプルで短くなります。 常に良いとは限りませんが、それでもなお。
- これにより、アルゴリズムがよりクリーンになります。 結合された関数のみに焦点を当てることで、データで処理された引数に焦点を当てることなく、何が起こっているのかをより理解できます。
- これにより、変換中のデータよりも変換について考えるようになります。
- これは、さまざまな種類のデータを操作できるビルディングブロックとしての機能、特定の種類のデータを使用した操作についての考え方について考えるのに役立ちます。 データの引数を省略すると、より創造的になります。
おわりに
サイレントプログラミングとしても知られる冷酷なスタイルは、コードをよりクリーンで考えやすくすることができます。 コードをリファクタリングして、すべての変換を単一の関数に結合することにより、多くの場所で使用できる小さなビルディングブロックになります。
次へ
この例では、すべてをブラシレススタイルにリファクタリングできませんでした。 命令型スタイルで記述されたコードがまだあります。 このコードのほとんどは、オブジェクトと配列で機能します。
オブジェクトと配列を扱う宣言的な方法を見つける必要があります。 そして、免疫はどうですか? 不変のスタイルでオブジェクトと配列をどのように操作しますか?
このシリーズの次の投稿「 Immutable and Objects 」では、機能的で不変のスタイルでオブジェクトを操作する方法について説明します。 その後、「Immutable and Arrays」という投稿が公開されます。ここでは、配列に関して同じことが説明されます。