JavaScriptの機能プログラマーの紹介(作曲ソフトウェア)

スモークアートキューブからスモーク— MattysFlicks —(CC BY 2.0)
注:これは、JavaScriptES6 +で関数型プログラミングおよび合成ソフトウェア技術をゼロから学習する「Composing Software」シリーズ(現在の本です!)の一部です。乞うご期待。まだまだたくさんあります!
本を購入する|インデックス| <前へ|次へ>

JavaScriptまたはES6 +に不慣れな方のために、これは簡単な紹介として意図されています。初心者でも経験豊富なJavaScript開発者でも、新しいことを学ぶことができます。以下は、表面を傷つけて興奮させることを目的としています。もっと詳しく知りたい場合は、もっと深く探検する必要があります。まだまだ先があります。

コーディングを学ぶ最良の方法は、コーディングすることです。 CodePenやBabel REPLなどのインタラクティブなJavaScriptプログラミング環境を使用することをお勧めします。

または、ノードまたはブラウザーコンソールのREPLを使用して回避することもできます。

式と値

式は、値に評価されるコードの塊です。

JavaScriptで有効な式はすべて次のとおりです。

7;
7 + 1; // 8
7 * 2; // 14
'こんにちは'; // こんにちは

式の値には名前を付けることができます。そうすると、式が最初に評価され、結果の値が名前に割り当てられます。このために、constキーワードを使用します。これが唯一の方法ではありませんが、最もよく使用する方法なので、ここではconstを使用します。

const hello = 'Hello';
こんにちは; // こんにちは

var、let、およびconst

JavaScriptは、varとletの2つの変数宣言キーワードをサポートしています。私はそれらを選択の順番で考えるのが好きです。デフォルトでは、最も厳密な宣言constを選択します。 constキーワードで宣言された変数は再割り当てできません。最終値は、宣言時に割り当てる必要があります。これは厳格に聞こえるかもしれませんが、制限は良いことです。これは、「この名前に割り当てられた値は変更されない」ことを知らせるシグナルです。関数全体やブロックスコープを読む必要なく、名前の意味を完全に理解するのに役立ちます。

変数を再割り当てすると便利な場合があります。たとえば、より機能的なアプローチではなく、手動の命令型反復を使用している場合、letで割り当てられたカウンターを反復できます。

varは変数に関する情報が最も少ないため、最も弱い信号です。 ES6を使い始めてから、実際のソフトウェアプロジェクトで意図的にvarを宣言したことはありません。

変数がletまたはconstで宣言されると、再度宣言しようとするとエラーが発生することに注意してください。 REPL(読み取り、評価、印刷ループ)環境でより実験的な柔軟性が必要な場合は、constではなくvarを使用して変数を宣言できます。 varの再宣言は許可されています。

このテキストでは、実際のプログラムに対してデフォルトでconstを使用する習慣をつけるためにconstを使用しますが、インタラクティブな実験のためにvarを自由に置き換えてください。

タイプ

これまでに、数字と文字列の2つのタイプを見てきました。 JavaScriptには、ブール値(trueまたはfalse)、配列、オブジェクトなどもあります。他のタイプについては後で説明します。

配列は、値の順序付きリストです。多くのアイテムを収納できる箱と考えてください。配列リテラル表記は次のとおりです。

[1、2、3];

もちろん、それは名前を与えることができる表現です:

const arr = [1、2、3];

JavaScriptのオブジェクトは、キーと値のペアのコレクションです。また、リテラル表記もあります。

{
  キー:「値」
}

そしてもちろん、オブジェクトを名前に割り当てることができます:

const foo = {
  バー:「バー」
}

既存の変数を同じ名前のオブジェクトプロパティキーに割り当てたい場合は、そのショートカットがあります。キーと値の両方を提供する代わりに、変数名を入力するだけです。

const a = 'a';
const oldA = {a:a}; //長く冗長な方法
const oA = {a}; //甘いものを短く!

楽しみのために、もう一度やってみましょう。

const b = 'b';
const oB = {b};

オブジェクトは、簡単に一緒に新しいオブジェクトに構成できます。

const c = {... oA、... oB}; // {a: 'a'、b: 'b'}

これらのドットは、オブジェクトの広がり演算子です。 oAのプロパティを反復処理し、それらを新しいオブジェクトに割り当ててから、oBに対して同じことを行い、新しいオブジェクトに既に存在するキーをオーバーライドします。この記事の執筆時点では、オブジェクトスプレッドは新しい実験的な機能であり、すべての一般的なブラウザーではまだ使用できない可能性がありますが、機能しない場合は、Object.assign()を使用できます。

const d = Object.assign({}、oA、oB); // {a: 'a'、b: 'b'}

Object.assign()の例ではもう少し入力するだけで、多くのオブジェクトを作成している場合は、入力を節約することもできます。 Object.assign()を使用する場合、最初のパラメーターとして宛先オブジェクトを渡す必要があることに注意してください。プロパティがコピーされるオブジェクトです。忘れて、宛先オブジェクトを省略すると、最初の引数で渡したオブジェクトが変更されます。

私の経験では、新しいオブジェクトを作成するのではなく、既存のオブジェクトを変更することは通常バグです。少なくとも、エラーが発生しやすいです。 Object.assign()に注意してください。

破壊

オブジェクトと配列は両方とも、構造化をサポートしています。つまり、それらから値を抽出し、名前付き変数に割り当てることができます。

const [t、u] = ['a'、 'b'];
t; // 'a'
あなた; // 'b'
const blep = {
  blop: 'blop'
};

//以下は以下と同等です:
// const blop = blep.blop;
const {blop} = blep;
ブロップ; // 'blop'

上記の配列の例と同様に、一度に複数の割り当てに分解できます。多くのReduxプロジェクトで見られる行は次のとおりです。

const {タイプ、ペイロード} =アクション。

レデューサーのコンテキストでの使用方法は次のとおりです(このトピックの詳細については後述します)。

const myReducer =(状態= {}、アクション= {})=> {
  const {タイプ、ペイロード} =アクション。
  スイッチ(タイプ){
    case 'FOO':Object.assign({}、state、payload)を返します;
    デフォルト:状態を返す;
  }
};

新しいバインディングに別の名前を使用したくない場合は、新しい名前を割り当てることができます。

const {blop:bloop} = blep;
bloop; // 'blop'

読む:blep.blopをbloopとして割り当てます。

比較と三項

厳密な等価演算子(「トリプルイコール」とも呼ばれます)を使用して値を比較できます。

3 + 1 === 4; // true

ずさんな平等演算子もあります。正式には「等しい」演算子として知られています。非公式には、「double equals」です。 Double equalsには有効なユースケースが1つまたは2つありますが、代わりにデフォルトの===演算子を使用することをお勧めします。

他の比較演算子は次のとおりです。

  • >より大きい
  • <より小さい
  • > =以上
  • <=より小さいか等しい
  • !=等しくない
  • !==厳密に等しくない
  • &&論理的
  • ||論理的または

三項式は、コンパレータを使用して質問をすることができる式であり、式が真実かどうかに応じて異なる答えを評価します。

14-7 === 7? 「うん!」 : 'いいえ。'; //うん!

関数

JavaScriptには関数式があり、名前に割り当てることができます。

const double = x => x * 2;

これは、数学関数f(x)= 2xと同じことを意味します。大声で発声すると、その関数はxのfが2xに等しいことを読み取ります。この関数は、xの特定の値に適用する場合にのみ興味深いです。この関数を他の方程式で使用するには、4と同じ意味を持つf(2)を記述します。

つまり、f(2)= 4です。数学関数は、入力から出力へのマッピングと考えることができます。この場合のf(x)は、xの入力値と、入力値と2の積に等しい対応する出力値のマッピングです。

JavaScriptでは、関数式の値は関数自体です。

ダブル; // [関数:double]

.toString()メソッドを使用して関数定義を確認できます。

double.toString(); // 'x => x * 2'

関数をいくつかの引数に適用する場合は、関数呼び出しで呼び出す必要があります。関数呼び出しは、引数に関数を適用し、戻り値を評価します。

(argument1、argument2、... rest)を使用して関数を呼び出すことができます。たとえば、double関数を呼び出すには、かっこを追加し、値をdoubleに渡します。

double(2); // 4

一部の関数型言語とは異なり、これらの括弧は意味があります。それらがなければ、関数は呼び出されません:

ダブル4; // SyntaxError:予期しない番号

署名

関数には署名があります。

  1. オプションの関数名。
  2. カッコ内のパラメータタイプのリスト。オプションでパラメータに名前を付けることができます。
  3. 戻り値のタイプ。

型署名をJavaScriptで指定する必要はありません。 JavaScriptエンジンは、実行時にタイプを判別します。十分な手がかりを提供すれば、データフロー分析を使用して、IDE(統合開発環境)やTern.jsなどの開発者ツールによって署名を推測することもできます。

JavaScriptには独自の関数シグネチャ表記法がないため、いくつかの競合する標準があります。JSDocは歴史的に非常に人気がありますが、扱いにくい冗長性があり、ドキュメントのコメントをコードで最新に保つことを誰も気にしません。使用を停止しました。

現在、TypeScriptとFlowは大きな競争相手です。私はそれらのいずれかで必要なものをすべて表現する方法がわからないので、ドキュメント化の目的でのみRtypeを使用します。 HaskellのカレーのみのHindley–Milnerタイプに頼る人もいます。ドキュメンテーションのためだけに、JavaScript用に標準化された優れた表記法が見たいのですが、現在のところ、現在の解決策のどれも役に立たないと思います。今のところ、目を細めて、使用しているものとは少し違って見える奇妙なタイプシグネチャに追いつくために最善を尽くしてください。

functionName(param1:タイプ、param2:タイプ)=>タイプ

doubleのシグネチャは次のとおりです。

double(x:n)=>数値

JavaScriptでは署名に注釈を付ける必要はありませんが、関数の使用方法と構成方法について効率的に通信するには、署名とその意味を理解することが重要です。ほとんどの再利用可能な関数合成ユーティリティでは、同じ型シグネチャを共有する関数を渡す必要があります。

デフォルトのパラメーター値

JavaScriptはデフォルトのパラメーター値をサポートしています。次の関数は、未定義で呼び出すか、単に引数をまったく渡さない限り、恒等関数(渡した値と同じ値を返す関数)のように機能します。代わりにゼロを返します。

const orZero =(n = 0)=> n;

デフォルトを設定するには、上記のn = 0のように、関数シグネチャで=演算子を使用してパラメータに単純に割り当てます。この方法でデフォルト値を割り当てると、Tern.js、Flow、TypeScriptなどの型推論ツールは、明示的に型注釈を宣言しなくても、関数の型シグネチャを自動的に推論できます。

その結果、エディターまたはIDEに適切なプラグインがインストールされていれば、関数呼び出しを入力しているときに関数シグネチャをインラインで表示できます。また、コールシグネチャに基づいて関数を使用する方法を一目で理解することもできます。意味のあるところならどこでもデフォルトの割り当てを使用すると、より自己文書化されたコードを書くのに役立ちます。

注:デフォルトのパラメータは、関数の.lengthプロパティにはカウントされません。このプロパティは、.length値に依存するautocurryなどのユーティリティを無効にします。一部のカレーユーティリティ(lodash / curryなど)では、この制限にぶつかった場合にこの制限を回避するためにカスタムアリティを渡すことができます。

名前付き引数

JavaScript関数は、オブジェクトリテラルを引数として受け取り、名前付き引数に相当するものを実現するために、パラメーターシグネチャで構造化割り当てを使用できます。デフォルトのパラメーター機能を使用して、パラメーターにデフォルト値を割り当てることもできます。

const createUser =({
  名前=「匿名」、
  avatarThumbnail = '/avatars/anonymous.png'
})=>({
  名、
  アバター
});
const george = createUser({
  名前:「ジョージ」、
  avatarThumbnail: 'avatars / shades-emoji.png'
});
ジョージ;
/ *
{
  名前:「ジョージ」、
  avatarThumbnail: 'avatars / shades-emoji.png'
}
* /

休息と広がり

JavaScriptの関数の一般的な機能は、rest演算子を使用して、関数シグネチャの残りの引数のグループを収集する機能です:...

たとえば、次の関数は単純に最初の引数を破棄し、残りを配列として返します。

const aTail =(head、... tail)=> tail;
aTail(1、2、3); // [2、3]

Restは、個々の要素を配列にまとめます。 Spreadは反対のことを行います:要素を配列から個々の要素に広げます。このことを考慮:

const shiftToLast =(head、... tail)=> [... tail、head];
shiftToLast(1、2、3); // [2、3、1]

JavaScriptの配列には、スプレッド演算子が使用されたときに呼び出されるイテレーターがあります。配列内の各アイテムに対して、反復子は値を配信します。式[... tail、head]では、反復子は各要素を末尾配列から周囲のリテラル表記で作成された新しい配列に順番にコピーします。ヘッドはすでに個々の要素であるため、配列の最後に挿入するだけで完了です。

カレー

カリー化された関数は、一度に複数のパラメーターを受け取る関数です。パラメーターを受け取り、すべてのパラメーターが提供されるまでアプリケーションを完了し、最終値が返されます。

カレーと部分的なアプリケーションは、別の関数を返すことで有効にできます。

const highpass = cutoff => n => n> = cutoff;
const gt4 = highpass(4); // highpass()は新しい関数を返します

矢印機能を使用する必要はありません。 JavaScriptには関数キーワードもあります。 functionキーワードはより多くのタイピングが可能なため、矢印関数を使用しています。これは、上記のhighPass()定義と同等です。

const highpass = function highpass(cutoff){
  戻り関数(n){
    return n> = cutoff;
  };
};

JavaScriptの矢印は、おおよそ「機能」を意味します。使用する関数の種類に応じて、関数の動作にいくつかの重要な違いがあります(=>独自のthisがなく、コンストラクターとして使用できません)。今のところ、x => xと表示されたら、「xを取りxを返す関数」と考えてください。したがって、const highpass = cutoff => n => n> = cutoff;と読むことができます。として:

「ハイパスはカットオフを取り、nを取り、n> = cutoffの結果を返す関数を返す関数です」。

highpass()は関数を返すので、それを使用してより特殊な関数を作成できます。

const gt4 = highpass(4);
gt4(6); // true
gt4(3); // false

Autocurryを使用すると、機能を自動的にカリー化して、最大限の柔軟性を実現できます。関数add3()があるとします:

const add3 = curry((a、b、c)=> a + b + c);

autocurryでは、いくつかの異なる方法で使用できます。渡す引数の数に応じて正しい結果が返されます。

add3(1、2、3); // 6
add3(1、2)(3); // 6
add3(1)(2、3); // 6
add3(1)(2)(3); // 6

Haskellのファンには申し訳ありません。JavaScriptには自動通貨メカニズムが組み込まれていませんが、Lodashからインポートできます。

$ npm install --save lodash

次に、モジュールで:

「lodash / curry」からカレーをインポートします。

または、次の魔法の呪文を使用できます。

//小さな再帰的オートカリー
const curry =(
  f、arr = []
)=>(... args)=>(
  a => a.length === f.length?
    f(... a):
    カレー(f、a)
)([... arr、... args]);

機能構成

もちろん、関数を作成できます。関数構成は、ある関数の戻り値を別の関数の引数として渡すプロセスです。数学表記で:

f g

これはJavaScriptでこれに変換されます:

f(g(x))

内部から評価されます。

  1. xが評価されます
  2. g()はxに適用されます
  3. f()はg(x)の戻り値に適用されます

例えば:

const inc = n => n + 1;
inc(double(2)); // 5

値2はdouble()に渡され、4が生成されます。4は5に評価されるinc()に渡されます。

任意の式を関数の引数として渡すことができます。式は関数が適用される前に評価されます:

inc(double(2)* double(2)); // 17

double(2)は4に評価されるため、inc(4 * 4)として読み取ることができ、inc(16)に評価され、その後17に評価されます。

関数構成は、関数型プログラミングの中心です。後でさらに詳しく説明します。

配列

配列にはいくつかの組み込みメソッドがあります。メソッドは、オブジェクトに関連付けられた関数です。通常、関連付けられたオブジェクトのプロパティは次のとおりです。

const arr = [1、2、3];
arr.map(double); // [2、4、6]

この場合、arrはオブジェクト、.map()は値の関数を持つオブジェクトのプロパティです。これを呼び出すと、関数が引数に適用されるほか、これが呼び出され、メソッドが呼び出されると自動的に設定される特別なパラメーターが適用されます。 this値は、.map()が配列のコンテンツにアクセスする方法です。

double関数を呼び出すのではなく、値としてmapに渡すことに注意してください。これは、mapが関数を引数として受け取り、それを配列内の各アイテムに適用するためです。 double()によって返された値を含む新しい配列を返します。

元のarr値は変更されていないことに注意してください。

arr; // [1、2、3]

メソッド連鎖

メソッド呼び出しを連鎖させることもできます。メソッドチェーンは、名前で戻り値を参照する必要なく、関数の戻り値でメソッドを直接呼び出すプロセスです。

const arr = [1、2、3];
arr.map(double).map(double); // [4、8、12]

述語は、ブール値(trueまたはfalse)を返す関数です。 .filter()メソッドは述語を取り、新しいリストを返します。新しいリストに含まれる述語を渡す(trueを返す)項目のみを選択します。

[2、4、6] .filter(gt4); // [4、6]

多くの場合、リストからアイテムを選択し、それらのアイテムを新しいリストにマッピングします。

[2、4、6] .filter(gt4).map(double); [8、12]

注:このテキストの後半では、トランスデューサと呼ばれるものを使用して、より効率的な選択とマッピングを同時に行う方法について説明しますが、最初に検討することがあります。

結論

頭が今回転していても心配しないでください。さらに多くの調査と検討に値する多くのことの表面をかろうじてかきました。これらのトピックの一部については、後ほど詳しく説明します。

本を購入する|インデックス| <前へ|次へ>

EricElliottJS.comで詳細を見る

EricElliottJS.comのメンバーは、インタラクティブなコードチャレンジのビデオレッスンを利用できます。メンバーでない場合は、今すぐサインアップしてください。

Eric Elliottは、分散システムの専門家であり、「Composing Software」および「Programming JavaScript Applications」という本の著者です。 DevAnywhere.ioの共同創設者として、彼は開発者にリモートで作業し、ワーク/ライフバランスを受け入れるために必要なスキルを教えています。彼は暗号化プロジェクトの開発チームを構築して助言し、Adobe Systems、Zumba Fitness、The Wall Street Journal、ESPN、BBC、およびUsher、Frank Ocean、Metallicaなどのトップレコーディングアーティストのソフトウェアエクスペリエンスに貢献しています。

彼は世界で最も美しい女性との遠隔生活を楽しんでいます。