Program
Overview
このセクションでは、さまざまなプログラミング言語とその使用方法について詳しく解説しています。
それぞれの言語の基本構文、特徴、用途、実践的なコーディングテクニックなどを網羅し、開発者が効率的にプログラムを構築できるようサポートします。
また、各言語ごとのベストプラクティスや、他の技術との統合方法についても説明します。
セクション内容
- 言語別ガイド: 各プログラミング言語の基本情報と詳細なガイド。
- コーディングテクニック: 効果的なコーディング手法、デザインパターン、最適化技術。
- 実践的な使用例: 具体的なプロジェクトでの使用例とサンプルコード。
- 比較と選定: 異なる言語の比較、適切な言語選定の基準。
Program 計算量を求める
特に Node.js サーバーをシングルスレッドで運用する場合、 O(n^2) のような計算量を要求するような処理は、 たった1つのリクエストが他のリクエストを阻害してしまうので致命的な不具合となりえます。
フレームワーク ベンチマーク
元のURL Web Framework Benchmarks
言語種類
コンパイルされて実行できる状態になったプログラムのことを実行形式と呼ぶ
スクリプト言語(scripting language):軽量言語 または LL(Lightweight Language)
一般的に軽量言語(LL)ではメモリ管理のコストなどに注意してコードを構成しなければパフォーマンスが極端に悪くなることがある。
※Goは細部を気にせずコードを書いても、比較的パフォーマンスを引き出しやすい傾向がある。
マクロ言語
ソフトウェアの動作の自動化などを行なうプログラムを記述する言語はマクロ言語と呼ばれることがある。
静的型付けのメリット
- 型安全性
- ドキュメント化と入力保管 静的型付けの言語では型の情報がソースコードに書かれる。この情報はDXをかなり助ける。
変数/メソッド名 コメント
原則使わなくて済む方が良い。
- コメントなしで意味が伝わる命名をする
- 関数の引数は命名だけではなく、TypeScriptの情報量を増やす
メソッド名
メソッド名の場合は動詞や助動詞を用いて命名する このパターンで適宜考えないといけない
- 真偽値を返すメソッド
- 必要に応じてしか実行されない処理をするメソッド
- 非同期処理に関連するメソッド
- コールバックメソッド
- コレクションの操作に関するメソッド
- 状態に関するメソッド
- オブジェクトのライフサイクルを扱うメソッド
- データに関するメソッド
クラス名
クラス名は何らかし らの責務を持つモノとして捉えるため名詞用いる
コンストラクター
コンストラクターに値を渡すってことは依存するということと同義
それを解決するのが DI
interface
インターフェースの名前に形容詞を用いることがある。形容詞を用いることでクラスの持つ性質を説明できるようになる。
記号の呼び名
汎用的に呼ばれているものを記載する
[]: ブラケット
プロセスとスレッド
プロセスとスレッドについての勉強会
シングルスレッドとマルチスレッド
プロセス(docker.mdで図 がある。)
OSから見える処理の単位。
- 1プロセス1CPUの関係。
- メモリ空間を共有していない
- 他のプロセスのメモリにはアクセスできない
- プロセス間の同期が難しい
- スレッドと比較して1プロセス毎の情報量が多い
- 情報量が多いので切り替えが遅い
- マルチスレッドプログラムと比較してメモリ効率が低い
- 仮想アドレスと物理アドレスの解決が高コストのため切り替えが遅い
- マルチスレッドプログラムと比較して大量のメモリが使用できる
- マルチプロセッサの場合、(一般的に)シングルプロセスマルチスレッドプログラムよりもマルチプロセスプログラムの方が高速
スレッド(糸)
プロセスの中で並列的に処理を行う仕組み
- メモリ空間を共有している
- 他のスレッドのメモリにアクセスできる
- スレッド間の同期が容易
- スレッドセーフな実装にする必要がある
- メモリを共有しているので、他のスレッドに予期せぬ影響を与えてしまうことがある
- プロセスと比較して、1スレッド毎に必要な情報量が少ない
- 情報量が少ないので切り替えが早い
- マルチプロセスプログラミングと比較してメモリ効率が高い
- マルチプロセスプログラミングと比較して大量のメモリ確保が行えない
- 非同期に実行できる処理がある場合は、(一般的に)シングルスレッドプログラムよりもマルチスレッドプログラムの方が高速
実際のプログラムでは、プロセスやスレッドを都度生成する事は稀です。 高コスト、メモリリーク、生成できない可能性がある等の問題があるからです。 起動時に必要数を確保しプログラム終了時まで破棄しないスレッドプール等の仕組みが使われます。
式と文
プログラムを組み立てるのに必要な式と演算子
- 式 式とはリテラルや変数、演算子、メソッドなどを組み合わせたもの 式は何らかの値を返す
- 文 文(Statement)とは値を返さないもの(=変数に代入できないもの)
ひとつの文章にまとめない方がいい
以下の書き方は良い習慣ではない。 というのも一般的な開発環境(デバッガー)ではコードの実行を中断しブレークポイントは行単位でしか設定できない そのため短い文であっても複数の文を1行にまとめないが鉄則
name = '山田'; puts(name)
リテラル
リテラルは数値や文字列などのデータをコード中で表現する方法
- リテラル表現
108, 'Hoge', [1,2,3]
のようなリテラルをかくことは、すべて特定の型(クラス)に応じたインスタンスを生成することと同義。 この例だtとInteger, String, Arrayクラスのインスタンスが 生成される。 リテラル表現はインスタンスを生成する手軽な手段だが、すべての型(クラス)で用意されているわけではない。 数値、文字列などよく利用するものについていちいち利用するたびにインスタンス化を意識させるのは面倒なためより直接的に表現できるリテラルが用意されているだけ。 一般的なクラスはnewというメソッドを利用してインスタンス化する。
第一級オブジェクト(ファーストクラスオブジェクト)
第1級オブジェクトというのは数値や文字列のような、変数に割り当てられたり、なんらかの計算処理ができたりといった、プログラミングの基本機能が使える対象のことを指す。
副作用
副作用とは、関数の外部状態を変更したり、コンソールに出力したり、例外を投げたりすることを指す。
副作用があると同じ引数を渡しても何度実行しても結果が異なることがある。
関数
関数にも種類がある
- 純粋関数
- 副作用を持つ関数
純粋関数(pure function)
純粋関数(pure function)とは、与えられた引数から常に同じ結果を返す関数であり副作用がない関数のことを指す。
純粋関数は、引数に渡された値だけを使って計算を行い、値を返す関数で、外部の状態を変更する副作用を持たない。
副作用を持たないため、純粋関数は安全で予測可能で、テストしやすく、再利用性が高いとされている。
純粋関数には以下のような特徴がある。
- 同じ入力に対しては必ず同じ出力を返す
- 関数外の状態に影響を与えない
- 副作用を起こさない
メリット
- テストのしやすくなる。
- デバッグがしやすくなる
- 再利用性の高くなる。
- また、副作用のない関数は、並列実行に適しているため、分散処理などの分野での利用も期待されている。
関数型プログラミングとの関わり 純粋関数は関数型プログラミングの中心的な考え方であり、関数型プログラミングでは、副作用を最小限に抑え、純粋関数を使ってプログラムを構築することが推奨されている。
副作用のある関数
副作用がある関数は同じ引数に対して異なる結果を返す可能性があるため、テストがしにくかったり、デバッグがしにくかったりする。
また、副作用を伴う関数は、実行順序によって結果が異なる場合があるため、コードの予測性が低下することがあります。
// 例: userオブジェクトの状態で状態で、常に同じ出力を返すわけではないため副作用のある関数になる
const isLatestTerm = async (user: User): boolean => {
user.accept ? true : false
};
純粋関数参考
以下に、引数を直接変更する例と新しい値を返す例を示します。
引数を直接変更する例:
function addToCart(item: Item, cart: Cart): void {
cart.items.push(item);
cart.total += item.price;
}
この関数は、引数の cart
オブジェクトの items
プロパティと total
プロパティを変更しています。これは副作用を持つ関数です。
新しい値を返す例:
function addToCart(item: Item, cart: Cart): Cart {
return {
items: [...cart.items, item],
total: cart.total + item.price,
};
}
この関数は、引数の cart
オブジェクトを変更するのではなく、新しい Cart
オブジェクトを返します。これは副作用を持たない関数であり、純粋関数と呼ばれます。
非同期関数
非同期関数は副作用を持つ可能性がある。
非同期関数は 、データベースやAPIサービスなど、外部の状態を変更する可能性があるため。
また、非同期関数は時間がかかる場合があり、呼び出し元の処理が一時停止することがあるため、副作用を持つ関数と考えることができる。
ただし、非同期関数が副作用を持つとは限らない。
たとえば、配列のソートなど、引数に渡された値自体を変更しない非同期関数もあるため。
これらの関数は副作用を持たず、純粋関数と同様に扱うことが可能。
関数とサブルーチンの違い
関数は値を返すのに対してサブルーチンは値を返さないという違い。 どちらも同じようなものであるものである(実際にほとんどの言語でサブルーチンと関数の区別は存在しない)
サブルーチン
サブルーチンとは**メインの処理(ルーチン)**から呼び出される別の処理という意味合いで、多くの場合関数と同じ意味で使われる。
メソッド
クラスの中に定義された関数のことをメソッドと呼ぶ。
インスタンスメソッドとクラスメソッド
- お互いの理解
インスタンスメソッドはインスタンスの情報を取得・操作するためのもの クラスメソッドはクラスの情報を取得/操作するためのもの
クラスメソッドと静的なメソッド
クラスメソッドは、特定のクラスに関連付けられたメソッドであり、そのクラスのインスタンスではなく、クラス自体に対して呼び出される。 同様に、静的なメソッドもクラスに直接関連付けられ、インスタンスではなくクラス自体に対して呼び出される。
静的なメソッドとクラスメソッドの主な共通点は次のとおりです:
-
インスタンスに依存しない: 静的なメソッドやクラスメソッドは、インスタンスの状態に依存しません。そのため、インスタンスを生成せずに直接呼び出すことができます。
-
クラスから直接呼び出し: 静的なメソッドやクラスメソッドは、クラス名を使って直接呼び出すことができます。インスタンスを生成する必要がないため、オブジェクトの生成やインスタンス化の手間が省けます。
-
インスタンス変数にアクセスできない: 静的なメソッドやクラスメソッドは、インスタンス変数に直接アクセスすることはできません。代わりに、クラス変数や他の引数を使用してデータの受け渡しを行う必要があります。
高階関数(こうかいかんすう)(Higher-Order Function)
高階関数は、関数を引数として受け取り、関数を返す関数のことを指します。
- 関数を引数として受け取ることができる。
- 関数を戻り値として返すことができる。 たとえば、特定の操作を実行する前にログを出力する高階関数を作成できる。 ※今思えば関数をwrapして操作を実行すればよかった(クラス型で描くよりも)
高階関数は、関数型プログラミングの基本的な概念のひとつであり、多くの場面で活用されます。高階関数を使用することで、コードの再利用性や柔軟性を高め、より抽象的な操作を行うことができます。
とくにJavaScriptやTypeScriptなどの関数型プログラミングがサポートされている言語では、高階関数がよく使用される。
※高階関数は、最初に呼び出された段階では内部の関数が実行されないという特徴がある(つまりセットする感じ)
// 一番簡単な例
function sayHello() {
return () => {
console.log('hello!');
}
}
コールバック関数
広義だと単なる「高階関数に渡すための関数」 自分で直接実行するのではなく、相手に実行してもらうのがコールバック関数
CLIを作りたいのであれば
コマンドラインからの引数をparseする考えを持つ。
コマンドラインツール用に引数をパースするライブラリ rubyのcommanderをインスパイアして作られている
頻繁に発生するオペレーション作業を自動化するなど、ちょっとしたCLIをツールを自作したいケースはよくあると思います。Shellでサクッと終わらせてもよいのですが、Web APIを利用するようなケースなど、高級言語で開発した方が効率か良いですし、副産物としてマルチOSで動作させられるおまけがついてきます。どうせやるなら今っぽいCLIを作りたい。ということで、Vue.js、Reactを参考に、commanderモジュールを使ったNodejsでのCLI実装について紹介します。
ミュータブルとイミュータブルの違い
ミュータブル オブジェクトをそのままに中身だけを変更できることを意味する 。
イミュータブル 一度作成したオブジェクトの中身を書き換えることはできない。
- ミュータブル 参照先の変わる更新と変わらない更新がある。
参照先の変わる更新
data = [1, 2, 3]
data = [4, 5, 6] # 新たな参照先が格納される
参照先の変わらない更新
data = [1, 2, 3]
data[2] = 6 # 参照先の差している配列のインデックスを更新する
p data # [1, 2, 6]
- イミュータブル プログラミング言語によって変わると思うがプリミティブ型はイミュータブル
num1 = 5
num1 += 1 # 値の更新によって常に参照先が変わる。
レシーバー(receiver)
メソッド呼び出しの文脈で「.」の前に位置するオブジェクトのことをレシーバーと呼ぶこともある。 オブジェクト指向ではメソッドを呼び出すとはオブジェクトに対して何らかの司令/メッセージを送信することと見なされる つまりそのメッセージの受け取り手が(receiver)オブジェクトになる。
プログラミングパラダイム
オブジェクト指向型や手続き型のようなプログラムの見方・考え方の枠組みのことをプログラミングパラダイムという
種類
- 構造化プログラミング
- 宣言型プログラミング
- イベント駆動型プログラミング
- スタック指向プログラミング
- コンポーネント指向プログラミング
- アスペクト指向プログラミング(AOP)
命令型は死後の様子。
今は命令型に手続きの定義と呼び出しを加えることで手続き型と同じように扱われている模様。
宣言型
宣言型、命令型、手続き型の違いを考える
命令型と宣言型の違い
宣言型プログラミング(Declarative Programming)とはプログラムが「何をするか」を記述することに焦点を当てる。
つまりプログラムの目的や結果について述べる方法。
具体的には、必要な結果を宣言し、システムがそれを達成する方法や手順については明示的に指示しません。
代表的な宣言型プログラミングの例としては、関数型プログラミング(Functional Programming)やデータベースクエリ言語があります。
主な特徴
- 目的や結果に焦点を当てる。
- 処理の手順や制御フローを明示的に指示しない。
- データに関する操作や変換を中心に記述する。
- 再利用性が高く、複雑さや副作用を減らすことができる。
例 SQL(データベースクエリ言語):データベースから情報を取得するためのクエリを記述する際、どのようなデータを取得するかを宣言する。
命令型
命令型プログラミング(Imperative Programming)とは**プログラムが「どのようにするか」**を具体的に指示することに焦点を当てる。
プログラムはステートメントや手続きのシーケンスで構成され、コンピューターに対して明確な命令や手順を与えます。プログラムは手続き的に実行され、状態の変更や制御フローが明示的に表現されます。代表的な命令型プログラミングの例としては、手続き型プログラ ミング(Procedural Programming)やオブジェクト指向プログラミング(Object-Oriented Programming)があります。
主な特徴
- プログラムが具体的な命令や手順を与える。
- プログラムの状態を変更し、制御フローを明示的に表現する。
- 変数やデータの状態を操作することが一般的。
- 再利用性が低く、副作用や状態がある
例 C言語やJavaなどの手続き型プログラミング言語では、プログラムの処理手順を明示的に指定し、変数やデータを操作して問題を解決します。
手続型
手続型プログラミングは、命令型プログラミングの一部であり、手続きや手続きの呼び出しに焦点を当てるプログラミングスタイル。
手続きとは、一連の操作や処理手順のことを指し、手続きはプログラム内で再利用可能な単位として扱われます。
主な特徴
- プログラムを手続き(手続きまたは関数)の集まりとして組織化する。
- 手続きの呼び出しによってプログラムの処理を実行する。
- ステートメントや手続きのシーケンスに焦点を当てる。
- 変数やデータの状態を明示的に操作する。
例
手続き型プログラミングの例として、PascalやFORTRANなどの古いプログラミング言語が挙げられる。
これらの言語では、プログラムを手続きの集まりとして組み立てることが一般的でした。手続きはプログラム内で再利用され、特定の処理やタスクを実行するために呼び出されました。
手続き型プログラミングは、命令型プログラミングの一部であり、手続きの呼び出しや手続きの定義に焦点を当てたプログラミングスタイルです。このスタイルでは、手続きのシーケンスや制御フローを明示的に指定し、変数やデータの状態を操作してプログラムを実行します。
最近は手続き型よりも宣言型の方が優れている(?)ような風潮があるのはなぜか?
宣言型は参照透過性があるので、同じ引数で呼び出しを行えば、同じ結果を返すことを保証できる 手続き型の場合は参照透過性がないため、同じ結果を返すとは限らない -> つまりDDDでいうドメインモデルは宣言型ではないということか?
同期処理と非同期処理
多くのプログラミング言語にはコード評価の仕方として同期処理(sync)と非同期処理(async)という大きな分類がある。
並行と並列
並行(concurrent)
横に移動してスレッドを処理する。意味がわかりやすい。
複数のタスクを一人でタスクスイッチしながら同時に捌いているイメージ。
つまり、ある瞬間に2つの作業を同時に行なっているというわけではない
並列(parallel)
複数の作業が本当の意味で同時に進行している状態。
これが本当の意味で複数
並行と並列の違い
定義
システムが複数の動作(処理の流れ)を同時に実行状態(in progress)に保てる機能を備えている場合を 並行(concurrent)と言い、複数の動作を同時に実行できる場合を並列(parallel)と言います。 重要な概念、違いは 「実行状態」という点です。
-
並行(concurrent) ある任意時間時でひとつの仕事しか行なわないが、復数の仕事を切り替えることにより同時に実行すること。
複数の仕事(スレッド)を実行状態として保てている状態。 -
並列(parallel) ある任意時間時で複数の仕事を同時に実行すること。
並列は並行を包含していることに注意。
型精査方法
参考URL 型の互換性判断について2種類ある。
公称型 - 継承関係に着目する(nominal typing)
公称型では型の互換性はオブジェクト同士の継承関係(is-a関係)に着目して判断している。 JavaやPHPなどはこの型システムを採用している。
例えば、AnimalとUserの2つのクラスが、全く同じnameプロパティを持っていて、同じように取り扱えたとしても、お互いに継承関係が無い場合は、User型の変数にAnimalインスタンスを代入することも、その逆もできません。互換性がないわけです。逆に、CatクラスがAnimalを継承している場合、Animal型の変数にCatは代入可能 です。CatはAnimalとの互換性があることになります。
つまり同じ継承先(親)を継承していれば互換性があるとみなす。
あくまで、継承関係があるかが大事。「能力」よりも「血統」重視の型システムと言えそうです。
- TypeScriptでも公称型を実現したい 参考URL
- 適当なプロパティを増やしてまったく同じ構造へならないようにする。
type Book = { name: string, hoge: any }
type Person = { name: string, fuga: any }
- シンボルを使用 うっかり事故は防げない。そのためシンボルを使用する。
const bookNominality = Symbol()
type Book = { name: string, [bookNominality]: any }
const personNominality = Symbol()
type Person = { name: string, [personNominality]: any }
// 上で定義したシンボルは実行時には不要のため、コンパイル後のJSコードに残ら内容declareする
declare const bookNominality: unique symbol
type Book = { name: string, [bookNominality]: any }
declare const personNominality: unique symbol
type Person = { name: string, [personNominality]: any }
構造的部分型 - プロパティに着目する(structural sub typing)
構造的部分型は継承関係ではなくオブジェクトが持っているプロパティが互換しているかどうかに着目して判断している。 つまり構造が同じかどうか。 TypeScriptやGolang?が採用している。
例えば、AnimalとUserの2つのクラスに、継承関係がなかったとしても、同じnameプロパティを持っていれば、User型の変数にAnimalを代入できます。その逆も可能です。つまり、お互いに互換性があるわけです。
class Animal {
public name: string = ''
}
class User {
public name: string = ''
}
let user: User = new User()
let animal: Animal = new Animal()
user = animal // OK
animal = user // OK
「血統」よりも「能力」重視なのが構造的部分型と言えそうです。
スコープ
グローバルとローカルがある。
これについては、実は各プログラミングごとに決まってい て、それぞれ「レキシカルスコープ(静的スコープ)」と「ダイナミックスコープ(動的スコープ)」と呼ばれています。
レキシカルスコープ(静的)
レキシカルスコープとは、変数のスコープがコードが書かれている場所によって決まるというもの
JS,Ruby,Java,Python
などが該当
レキシカルスコープは関数を定義した時点でスコープが決まる。
- JSにアロー関数ができた理由
thisの束縛の話!通常の変数は「レキシカル(静的)」だけどthisは「ダイナミック(動的)」だ!
これを無くしたい、という意図でアロー関数は作られた
JavaScript(およびTypeScript)はレキシカルスコープを採用しています。
ダイナミックスコープ(動的)
ダイナミックスコープは変数のスコープが実行時にどのように呼び出されたかによって決まります。
ダイナミックスコープは関数を実行した時点でスコープが決まります。
Reflection
プログラムの実行過程でプログラム自身の構造を読み取ったり書き換えたりする技術のこと。
DI(Dependency Injection)
DIを使う理由は、結合度の低下・てスタビリティの向上が主な理由。
キャスト
アップキャスト
extends元の基底クラスの型にキャストすることをアップキャストと呼ぶ TypeScriptのアップキャストは明示しないでできる。
class A {
a = 1;
}
class A2 extends A {
a2 = 2;
}
var a2 = new A2;
function testA(a: A) {
console.log(a.a); // 1を出力
}
testA(a2); // A2からAにアップキャスト
ダウンキャスト
ダウンキャストは明示する必要がある。明示しないとエラーになる。 extends先の派生クラス型にキャストすることをダウンキャストと呼ぶ。 ※継承先がダウンとする。
class A {
a = 1;
}
class A2 extends A {
a2 = 2;
}
var a = new A;
console.log((a as A2).a2); // as A2でダウンキャストを明示
プログラミングにおけるシグネチャ
プログラミングの分野では、関数やメソッドの名前、引数の数やデータ型、返り値の型などの組み合わせのことをシグネチャという。
言語によっては、同じクラスなどの中で名前が同じだが引数の型などが異なるメソッドを複数同時に宣言できる場合もあり、それらはシグネチャによって識別される。
インクリメンタルビルド
ビルド処理効率化のための仕様。 これはコー ドやデータソースの変更内容に基づき、更新が必要なHTMLファイルのサブセットのみを生成することで、ビルドの高速化を図る機能。
オブジェクト
const obj = {
// フィールド: プロパティ
a: 20
}
カリー化
複数の引数を持つ関数を、1つの引数を受け取る関数の連鎖にすること。 カリー化のメリットは引数を1つずつ処理していくことで、部分適用がしやすくなる。 つまり、関数を実行する際に共通化した処理を部分適用していくことで その関数をより汎用的に使えるようになるということができます。
ストリーム
ストリームとはデータを比較的小さい単位が連続したものと捉え上流から下流へ流れるものとみなしそのデータの入出力・送受信(途中段階を含む)を最小限の滞留とさせ低遅延処理となるように扱う形態を指す。
またその操作のための抽 象データ型を指す
シリアライズ
まず、シリアライズとは何かを説明します。 簡単に言うと、データをファイルとして保存できる形式に変換することを、シリアライズといいます。 通常、オブジェクトデータはオブジェクトグラフと呼ばれるデータ構造になっており、一つを保存すれば良い、とはならないようです。 リンク状になったデータを保存できるように直列に繋ぐ作業を、シリアライズという(らしい)
ポリモーフィック
特定の親モデルを持たずに、いろいろな親モデルを持つことができる関連付けの仕組みをポリモーフィックという。
クロージャー
クロージャー(Closure)は、関数とその関数が宣言されたレキシカル環境(スコープ)の組み合わせ。
クロージャーは、関数が外部のスコープから変数を「キャプチャ」する能力を持ち、その関数が宣言されたスコープ外で実行されたとしても、その変数にアクセスできる
TypeScript(およびJavaScript)でのクロージャーの一般的な使用例は、プライベ ート変数を持つ関数を作成すること。 これは、とくにデータをカプセル化し、外部からの直接的なアクセスを防ぐために役立つ。
function createCounter() {
let count = 0; // この変数はcreateCounterのスコープ内に閉じ込められている
return {
increment: () => {
count++;
console.log(count);
},
decrement: () => {
count--;
console.log(count);
}
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
クロージャーは、状態(この場合はcount変数)を保持する関数を作成する際に非常に便利。
また、クロージャーを使用すると、関数のプライベートな状態を作成し、その状態を関数の外部から直接変更することを防ぐことができる。
デコレーター
JavaScriptにコンパイルされた後は、デコレーターは一般的に関数として表現されます。
デコレーターは、通常、クラスやクラスのメソッドなどの関数定義の前に@記号を付けて使用されます。たとえば次のようなコードがあります。
function log(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = function(...args) {
console.log(`Calling "${name}" with`, args);
const result = original.apply(this, args);
console.log(`"${name}" returned`, result);
return result;
};
return descriptor;
}
class Calculator {
@log
double(n) {
return n * 2;
}
}
この例では、log
というデコレーター関数を定義し、@log
を用いて Calculator
クラスの double
メソッドにデコレーターを適用しています。コンパイルされたJavaScriptのコードは以下のようになります。
function log(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = function(...args) {
console.log(`Calling "${name}" with`, args);
const result = original.apply(this, args);
console.log(`"${name}" returned`, result);
return result;
};
return descriptor;
}
class Calculator {
double(n) {
return n * 2;
}
}
__decorate([
log
], Calculator.prototype, "double", null);
コンパイルされたJavaScriptのコードでは、__decorate
という関数が追加されています。この関数は、デコレーターを適用したメソッドに関する情報を受け取り、対応するプロパティディスクリプターを変更しています 。
つまり、デコレーターは静的な型チェックやIDEの自動補完などの恩恵を受けつつ、ランタイムでの動作を変更するためのシンタックスシュガー
破壊的メソッドと非破壊的メソッドの使い所
Rubyにおける破壊的メソッド(変数の元の値を変更するメソッド)の使用は、いくつかのシナリオで適切です。ただし、これらのメソッドを使用する際には、コードの可読性や意図しない副作用に注意する必要があります。以下に、破壊的メソッドを使用するいくつかの一般的なシナリオを示します:
-
メモリ効率の向上: 大きなデータ構造を扱う場合、破壊的メソッドはメモリ使用量を削減するのに役立ちます。たとえば、大きな配列や文字列を変更する場合、非破壊的メソッドは新しいオブジェクトを生成しますが、破壊的メソッドは元のオブジェクトを直接変更します。
-
パフォーマンスの向上: 非破壊的メソッドに比べて、破壊的メソッドはオブジェクトのコピーを作成しないため、パフォーマンスが向上する場合があります。これは、とくにループ内や大量のデータを扱う場合に重要です。
-
コードの簡潔化: 破壊的メソッドは、コードをより簡潔にすることが可能。
たとえば、array.sort!
はarray = array.sort
よりも短く書くことができます。 -
変数のスコープが限定されている場合: 変数のスコープがメソッドやブロック内に限定されている場合、その変数を破壊的に変更しても他の部分に影響を与えるリスクは低くなります。
破壊的メソッドを使用する際の注意点:
-
副作用: 破壊的メソッドは元のデータを変更するため、予期しない副作用を引き起こす可能性があります。とくに、複数の場所で同じオブジェクトに依存している場合、一箇所での変更が他の箇所に影響を与えることがあります。
-
可読性と保守性: 破壊的メソッドはコードの可読性を低下させ、バグの原因となることがあります。コードの意図が明確でない場合や、他の開発者がコードを理解しにくい場合は、非破壊的メソッドの使用を検討してください。
破壊的メソッドの使用は、その利点と潜在的なリスクを慎重に評価した上で行うべきです。また、コードのコメントやドキュメントを通じて、破壊的な操作が行われていることを明確に伝えることが重要です。
関数型プログラミングについて
関数型プログラミングをゼロからわかりやすく実用的に幅広い視点から解説!〜 圏論か らFRPの構築まで 🔷UNIT 1🔷 OVERVIEW
メモ化とは
Ruby(とくにRuby on Railsの文脈で)におけるオブジェクトのメソッド内でのメモ化は、その特定のオブジェクトインスタンスに対してのみ有効。
これは、オブジェクト指向プログラミングの基本原則に従う。各オブジェクトインスタンスは独立しており、その状態(インスタンス変数の内容)は他のインスタンスと共有されない。
メモ化のスコープ
-
一回のリクエスト内でのメモ化 Webアプリケーションにおいて、1回のリクエスト処理中に同じインスタンスのメソッドが複数回呼ばれる場合、メモ化は有効。 この場合、メモ化はそのリクエストを処理する特定のオブジェクトインスタンスに対してのみ適用されます。
-
複数のクライアント間でのメモ化 異なるクライアントからのリクエストは、通常、異なるオブジェクトインスタンスによって処理される。 したがって、1つのクライアントでのメモ化の結果は他のクライアントには影響しません。各リクエストは独立しており、その中で生成されるオブジェクトインスタンスも独立しています。
メモ化の例
class SomeService
def expensive_operation
@expensive_operation ||= calculate_expensive_operation
end
private
def calculate_expensive_operation
# 何か時間がかかる処理
end
end
この例では、SomeService
のインスタンスが1回のリクエスト中で expensive_operation
メソッドを複数回呼び出す場合、初回の呼び出しでの計算結果がメモ化され、以降の呼び出しでは再計算されません。
プロセス間でのメモ化の共有
-
プロセス間でのメモ化の共有は通常行われません: Ruby on Railsなどの多くのWebアプリケーションフレームワークでは、異なるリクエストは異なるプロセスやスレッドで処理されることが一般的 そのため、1つのプロセスで行われるメモ化は他のプロセスには影響しません。各プロセスは独自のメモリ空間を持ち、他のプロセスとのデータ共有は行われません。
-
アプリケーション全体でのデータ共有: アプリケーション全体でデータを共有する必要がある場合、データベース、キャッシュサーバ(例:Redis)、またはその他の共有ストレージシステムを使用するのが一般的。
これらのシステムは、異なるプロセスやリクエスト間でデータを共有するためのメカニズムを提供します。
結論
メモ化は、特定のオブジェクトインスタンス内での計算結果の一時的な保存として機能し、主にそのインスタンスのライフサイクル中、または1回のリクエスト処理中に有効です。異なるリクエストやプロセス間でデータを共有するためには、外部ストレージシステムを利用する必要があります。
-
ひとつのクライアントに対してメモ化を行いたい場合
||=
でメモ化をする -
メソッドの処理結果を各クライアントで共有したい場合 redisなどに格納する
はい、その通りです。関数の結果をRedisなどのキャッシュシステムに格納することで、その結果を異なるクライアントやプロセス間で共有し、再利用できます。これは特に計算コストが高い操作やデータベースへのアクセスを減らすために有効です。
Redisを使ったキャッシングのメリット
-
パフォーマンスの向上: 一度計算された結果をキャッシュすることで、同じリクエストに対するレスポンス時間を大幅に短縮できます。
-
リソースの節約: 同じデータへの複数のリクエストに対して、データベースや外部APIへの問い合わせを減らすことができます。
-
スケーラビリティの向上: データベースやサーバーへの負荷を減らすことで、アプリケーションのスケーラビリティが向上します。
実装の考慮点
-
キャッシュの有効期限: データの鮮度を保つために、キャッシュには有効期限を設定します。キャッシュされたデータが古くなりすぎないようにするためです。
-
キャッシュの一貫性: 元のデータが変更された 場合、キャッシュも更新するか削除する必要があります。これにより、古いまたは不正確な情報を提供するリスクを減らすことができます。
-
エラー処理: Redisサーバーへのアクセスに失敗した場合などのエラー処理を適切に行う必要があります。
例:RubyでのRedisを使用したキャッシング
require 'redis'
def fetch_expensive_data
redis = Redis.new
cached_data = redis.get('expensive_data_key')
return JSON.parse(cached_data) if cached_data
# 高コストのデータ取得処理
expensive_data = calculate_expensive_data
redis.set('expensive_data_key', expensive_data.to_json, ex: 3600) # 1時間の有効期限でキャッシュ
expensive_data
end
このコードでは、高コストのデータ取得処理の結果をRedisにキャッシュし、以降の同じリクエストではキャッシュからデータを取得します。これにより、複数のクライアント間で処理の結果を共有し、パフォーマンスを向上させることができます。
iterable(反復可能なオブジェクト)
反復可能なオブジェクトはJSにおいて要素1つずつ順番にアクセスできるオブジェクトを指す。
具体的には、そのオブジェクトが Symbol.iterator
プロパティに対応するメソッドを実装している場合、そのオブジェクトは「反復可能」
Polyfill(ポリフィル)
- 定義: 古い環境やブラウザで新しいJavaScript機能を利用できるようにするコード。
- 仕組み: グローバルオブジェクトに新し い機能を追加して、まるでその機能が元々その環境に存在していたかのようにします。
- 例:
Array.prototype.includes
をサポートしていないブラウザのために実装するコード。
Ponyfill(ポニフィル)
- 定義: 古い環境やブラウザで新しい機能を利用できるようにするコード。
- 仕組み: グローバルオブジェクトを汚染せず、代わりにその機能を直接提供する独立した関数として実装します。
- 例: グローバル
Array.prototype.includes
を変更せずに、arrayIncludes(array, value)
のような関数を提供するコード。
主な違い
- Polyfill: グローバルオブジェクトを汚染する。
- Ponyfill: グローバルオブジェクトを汚染しない。