1.最初のステップ2.機能を組み合わせる3.部分使用(カリー化)4.宣言型プログラミング5. Quintessential表記6.不変性とオブジェクト7.不変性と配列8.レンズ9.結論この投稿は、Ramda Style Thinkingというタイトルの関数型プログラミングシリーズの4番目のパートです。
3番目の部分では、部分的なアプリケーションとカリー化の手法を使用して、複数の引数を取ることができる関数の組み合わせについて説明しました。
小さな機能的なビルディングブロックを記述し、それらを結合し始めると、算術、比較、ロジック、フロー制御などのJavaScript演算子をラップする多くの関数を記述する必要があることがわかります。 退屈に思えるかもしれませんが、私たちはラムダの背後にいます。
しかし、最初に、少し紹介します。
命令型と宣言型
プログラミング言語と記述スタイルを分離するには、さまざまな方法があります。 これらは、静的タイピングと動的タイピング、インタープリター言語とコンパイル言語、高レベルと低レベルなどです。
別の同様の区分は、命令型プログラミングと宣言型です。
深く掘り下げることなく、命令型プログラミングは、プログラマーがコンピューターに何をすべきかを指示し、その方法を説明するプログラミングスタイルです。 命令型プログラミングは、毎日使用する多くの構造を提供します:フロー制御(
if
-
then
-
else
構文とループ)、算術演算子(
+
、
-
、
*
、
/
)、比較演算子(
===
、
>
、
<
など) .d。)、および論理演算子(
&&
、
||
&&
)。
宣言型プログラミングは、プログラマーがコンピューターに何をすべきかを指示し、彼らが望むものを説明するプログラミングスタイルです。 コンピューターはさらに、目的の結果を取得する方法を決定する必要があります。
古典的な宣言型言語の1つはPrologです。 Prologでは、プログラムは一連の事実と一連の推論規則で構成されています。 質問をすることでプログラムを開始すると、Prologの推論規則のセットは事実と規則を使用して質問に答えます。
関数型プログラミングは、宣言型プログラミングのサブセットと見なされます。 関数型プログラムでは、関数を宣言し、これらの関数を組み合わせて何をしたいのかをコンピューターに説明します。
宣言型プログラムでも、命令型プログラムで実行する同様のタスクを実行する必要があります。 フロー制御、算術、比較、およびロジックは、引き続き作業する必要がある基本的な構成要素です。 しかし、これらの構造を宣言的なスタイルで表現する方法を見つける必要があります。
宣言的な代替
命令型言語であるJavaScriptでプログラミングしているため、「通常の」JavaScriptコードを記述する際に標準の命令型構文を使用するのが普通です。
しかし、パイプラインなどの構造を使用して機能変換を記述する場合、必須の構造は作成されたコード構造に適合しなくなります。
この不快な状況から抜け出すために、ラムダが提供する基本的な構成要素のいくつかを見てみましょう。
算術
2番目の部分では、一連の算術変換を実装して、組立ラインを示します。
const multiply = (a, b) => a * b const addOne = x => x + 1 const square = x => x * x const operate = pipe( multiply, addOne, square ) operate(3, 4)
使用するすべての基本的なビルディングブロックの関数をどのように記述するかに注目してください。
Ramdaは、標準の算術演算の代わりに使用するための
加算 、
減算 、
乗算、および
除算関数を提供します。 したがって、自己記述関数を使用した場所でramd
multiply
を使用でき、カレーの
add
関数を利用して
addOne
を置き換えることができます。
addOne
、
multiply
を使用し
multiply
square
を記述することもできます。
const square = x => multiply(x, x) const operate = pipe( multiply, add(1), square )
add(1)
増分演算子(
++
)に非常に似ていますが、増分演算子は変数を変更して、突然変異を引き起こします。
最初の部分から学んだように、不変性は関数型プログラミングの主要な原則であるため、
++
またはその従兄弟である
--
を使用したくありません。
add(1)
と
subtract(1)
を使用して増減できますが、これら2つの操作は非常に一般的であるため、Ramdaは
incと
decを代わりに提供します。
したがって、パイプラインをもう少し単純化できます。
const square = x => multiply(x, x) const operate = pipe( multiply, inc, square )
subtract
は、値を否定するための二項演算子
-
代わりですが、まだ単項演算子があります。 マルチ
multiply(-1)
使用することもできますが、Ramdaはこのタスクを実行するための
否定関数を提供します。
比較
また、
第2部では、人が投票する資格があるかどうかを判断するための関数をいくつか作成しました。 そのコードの最終バージョンは次のとおりです。
const wasBornInCountry = person => person.birthCountry === OUR_COUNTRY const wasNaturalized = person => Boolean(person.naturalizationDate) const isOver18 = person => person.age >= 18 const isCitizen = either(wasBornInCountry, wasNaturalized) const isEligibleToVote = both(isOver18, isCitizen)
一部の関数では標準の比較演算子(この場合は
===
および
>=
)を使用していることに注意してください。 可能な限り、Ramdaはこれらすべての代替品も提供しています。
===
代わりに
equalsを、
>=
代わりに
gteを使用するようにコードを変換しましょう。
const wasBornInCountry = person => equals(person.birthCountry, OUR_COUNTRY) const wasNaturalized = person => Boolean(person.naturalizationDate) const isOver18 = person => gte(person.age, 18) const isCitizen = either(wasBornInCountry, wasNaturalized) const isEligibleToVote = both(isOver18, isCitizen)
Ramdaは
<
>
gt 、
<のlt <
および
<=
lteも提供します。
これらの関数は通常の順序で引数を取るように見えることに注意してください(最初の引数は2番目の引数よりも大きいのでしょうか?)。 これは、それらを単独で使用する場合は理にかなっていますが、関数を組み合わせる場合は混乱する可能性があります。 これらの関数は「データが最後に来る」という原則に違反しているため、組立ラインや同様の状況で使用する場合は注意が必要です。 そして、それが
フリップとプレースホルダー(
__ )の利点です。
equals
加えて
equals
2つの値がメモリ内の同じスペースへの参照であるかどうかを判断するための
同一性がまだあります。
===
は、主なアプリケーションのケースがいくつかあります。文字列または配列が空(
str === ''
または
arr.length === 0
)であることを確認し、変数が
null
または
undefined
かどうかを確認します。
Ramdaは 、両方の場合に便利な関数
isEmptyと
isNilを提供します。
ロジック
2番目の部分 (およびそれより少し高い
部分 )では、
&&
および
||
代わりに、
both
および
both
関数を使用しました。 。 また、場所の
complement
についても話しました
!
。
これらの結合された関数は、関数が同じ値の演算を結合するときに非常に機能します。 上記の
wasBornInCountry
、
wasNaturalized
および
isOver18
すべて、人物のオブジェクトに適用されます。
しかし、時々
&&
を適用する必要があります
||
そして
!
異なる意味に。 特別な場合のために、Ramdaは関数
and 、
orおよび
notを提供します。 私は次のように思う:
and
、
or
、およびvalueで動作せ
both
、
both
、、
or
both
が関数で動作する。
主に
||
デフォルト値を取得するために使用されます。 たとえば、次のように書くことができます。
const lineWidth = settings.lineWidth || 80
これは一般的なイディオムであり、ほとんどの場合機能しますが、「偽」を判断するためにJavaScriptロジックに依存しています。
0
が有効なパラメーターである場合はどうなりますか?
0
は偽の値であるため、行の値は80になります。
上記で学習した
isNil
関数を使用できますが、
isNil
はより論理的なオプション
defaultToがあります。
const lineWidth = defaultTo(80, settings.lineWidth)
defaultTo
は
defaultTo
の2番目の引数をチェックします。 チェックが失敗した場合、受け取った値を返します。そうでない場合は、渡された最初の引数を返します。
条件
実行の流れを制御することは、関数型プログラミングではそれほど重要ではありませんが、必要な場合があります。
最初の部分で説明した反復関数のコレクションは、ループのほとんどの状況を処理しますが、条件は依然として非常に重要です。
ifElse
年を取得して次を返す関数
forever21
作成しましょう。 しかし、彼女の名前が示すように、21歳から彼はその意味のままです。
const forever21 = age => age >= 21 ? 21 : age + 1
条件(
age >= 21
)と2番目のブランチ(
age + 1
)は両方とも
age
関数として書くことができることに注意してください。 最初のブランチ(
21
)を定数関数(
() => 21
)として書き直すことができます。 これで、
age
を受け入れる(または無視する)3つの関数ができます。
これで、
RamdaのifElse関数を使用できるようになり
ました 。これは、
if...then..else
またはそのより短い従兄弟である三項演算子(
?:
と同等
?:
。
const forever21 = age => ifElse(gte(__, 21), () => 21, inc)(age)
前述したように、比較関数はユニオン関数のようには機能しないため、ここでプレースホルダー(
__
)の使用を開始する必要があります。 代わりに
lte
を使用することもできます。
const forever21 = age => ifElse(lte(21), () => 21, inc)(age)
この場合、「21は
age
以下」と読む必要があります。 読みやすく、わかりにくいので、残りの投稿ではサロゲートバージョンを使用します。
定数
定数関数は、このような状況で非常に役立ちます。 ご想像のとおり、Ramdaはショートカットを提供します。 この場合、略語は
alwaysと呼ばれ
ます 。
const forever21 = age => ifElse(gte(__, 21), always(21), inc)(age)
Ramdaは、
T always(true)
および
always(false)
略語として
Tおよび
Fも提供します。
アイデンティティ
alwaysDrivingAge
別の関数を作成してみましょう。 この関数は、
age
を取得し、その値が
gte
16の場合にそれを返します
gte
未満の場合、16を返します。これにより、誰でも車を運転するのに十分な年齢のふりをすることができます。
const alwaysDrivingAge = age => ifElse(lt(__, 16), always(16), a => a)(age)
比較の2番目の分岐(
a => a
)は、関数型プログラミングのもう1つの典型的なパターンです。 これは「アイデンティティ」として知られて
います (
「アイデンティティ関数」という用語の正確な翻訳はわかりません。これを選択してください-およそのトランス )。 つまり、受け取った引数を単に返す関数です。
ご想像のとおり、Ramdaは
ID関数を提供します。
const alwaysDrivingAge = age => ifElse(lt(__, 16), always(16), identity)(age)
identity
は複数の引数を取ることができますが、常に最初の引数のみを返します。 最初の引数以外の何かを返したい場合、より一般的な
nthArg関数があります。 これは、
identity
を使用するよりもはるかに一般的ではありません。
「いつ」と「限りない」
論理分岐の1つがIDである
ifElse
式も典型的なパターンであるため、Ramdaはより短縮されたメソッドを提供します。
この場合のように、2番目のブランチがアイデンティティである
場合 、
ifElse
代わりに
ifElse
使用できます。
const alwaysDrivingAge = age => when(lt(__, 16), always(16))(age)
条件の最初のブランチがIDである場合、
unlessを使用できます。
gte(__, 16)
を使用するための条件を逆にすると、
gte(__, 16)
を使用できます。
const alwaysDrivingAge = age => unless(gte(__, 16), always(16))(age)
条件
Ramdaは、
switch
または
if...then...else
式チェーンを置き換えることができる
cond関数も提供します。
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)
Ramdaコードで
cond
を使用する必要はありませんでしたが、何年も前にLispで同様のコードを書いたので、
cond
は古い友人のように感じます。
おわりに
命令型コードを宣言型関数コードに変換するためにRamdaが提供する一連の関数を調べました。
次へ
お気付きかもしれませんが、私たちが書いた最後のいくつかの関数(
forever21
、
drivingAge
および
water
)はすべてパラメーターを受け取り、新しい関数を作成してから、この関数をパラメーターに適用します。
これは一般的なパターンであり、Ramdaはすべてをきれいな外観にするツールを提供します。 次の投稿「
Pointless Notation 」では、同様のパターンに従う関数を簡素化する方法を検討しています。