Pike
Pikeは、C言語に似た文法を持つ高水準プログラミング言語で、豊富なライブラリとオブジェクト指向プログラミング機能を備えています。高速な実行速度が特徴であり、Webアプリケーション、ネットワークプログラミング、データベース処理など、幅広い用途で利用されています。
チュートリアルの目的
[編集]本チュートリアルでは、Pikeの基本的な文法と機能を初心者向けにわかりやすく解説します。Pikeを学ぶことで、プログラミングの基礎を身につけ、実践的な開発に応用できるようになることを目指しています。このチュートリアルを通じて、Pikeの魅力や活用方法を理解し、その可能性を広げていくことができるでしょう。
Pikeの特徴
[編集]Pikeは、以下の特徴を持つ高水準で強力なプログラミング言語です。
- 高水準かつ強力:Pikeでは、非常に複雑な問題も簡単に解決できます。
- オブジェクト指向:大規模なプログラムを小さなモジュールに分割して記述できるため、全体を一度に記述するよりも管理が容易です。
- インタプリタ型:プログラムのコンパイルやリンクを必要とせず、即時実行が可能です。
- 高速なスクリプト言語:Pikeは、最速のスクリプト言語の1つとして知られています。
- ガベージコレクション:メモリ管理が自動化され、メモリリークやその他のメモリ関連のバグのリスクが低減されます。
- 拡張性:Pike自体だけでなく、CやC++で書かれたプラグインと統合することが可能です。
- 前方参照の解決:2パスコンパイラを採用しており、グローバル変数や関数、クラスの前方参照を自動的に解決します。
Pikeは、小規模なスクリプトから大規模なアプリケーションまで対応可能で、特にインターネットアプリケーションでの利用に最適です。例えば、世界的に使用されているWebサーバーのRoxen WebServerやCaudiumはPikeで開発されています。また、Pikeの高度なデータ型とソケットのサポートにより、ネットワークプログラミングにも優れた性能を発揮します。
さらに、PikeはGNU General Public License(GPL)、GNU Lesser General Public License(LGPL)、およびMozilla Public License(MPL)の下で提供されるフリーソフトウェアであり、UNIX、Linux、Solaris、macOS、Microsoft Windowsなど、多くのオペレーティングシステムで利用可能です。
Pikeの用途
[編集]Pikeは、Webアプリケーション、ネットワークプログラミング、データベース処理、ゲーム開発など、多岐にわたる用途に利用されています。
具体的には、以下のような場面で活用されています。
- WebサイトやWebアプリケーションのバックエンド処理
- ネットワークプログラミングによるサーバーやクライアントの実装
- データベース処理によるデータの取得や加工、分析
- ゲーム開発におけるAIプログラムの実装
- 暗号化やセキュリティ関連のプログラムの実装
Pikeと他のプログラミング言語
[編集]- C/C++
- PikeはC/C++に似た文法を持つ高水準プログラミング言語です。C++よりも簡単で安全に使用できます。また、C++よりも柔軟性があり、柔軟なプログラミングスタイルを許容します。ただし、インタプリタ型であり、C/C++よりも高速ではありません。また、多くの異なるCおよびC++コンパイラが存在する一方で、Pikeの実装は1つしかありません。
- C#
- PikeはC#に非常に似ています。実際には、PikeはC#の設計に影響を与えた言語の1つであると考えられています。2つの言語の主な違いは、PikeにはC#よりも優れた標準型システムがあり、型変換などの処理にライブラリを必要としないことです。C#に慣れている場合、Pikeは非常に使いやすいと思います。
- Java
- 表面的には Java に似ていますが、Java と同様に、Pikeは中間形式に変換され、それがインタプリタによって実行されます。Java プログラムは通常、この中間形式でユーザーに配信されますが、Pikeではソースコードを使用します。これは、Pikeにおいてコンパイル時間、つまりプログラムを中間形式に変換するのにかかる時間が事実上ゼロであるためです。
- Perl
- PerlはUnixシステム上でシステム管理ツールとして開始されました。PikeとPerlはどちらも文字列を扱うのに適しており、どちらもウェブサーバーの機能を追加するために使用できます。PerlはPikeよりもはるかに広く使用されています。一部のプログラマーは、Perlの構文がわかりにくいと感じています。
- Python
- Pythonで書かれたプログラムは、Pikeプログラムとは非常に異なって見えますが、PythonとPikeはアイデアや使用方法において似ています。Pythonはより広く使用されており、より多くのライブラリが利用可能です。一方、Pikeはより高速で、より高度な型システムを持ち、オブジェクト指向プログラミングに対するサポートもより良好です。PikeのよりC++に近い構文は、C++、C、またはJavaを知っているプログラマーにとっては始めやすくなっています。
- JavaScript
- JavaScriptとPikeはどちらもオブジェクト指向言語です。ただし、いくつかの重要な違いがあります。JavaScriptはプロトタイプベースであり、Pikeはクラスベースです。
- Ruby
- RubyとPikeは、どちらもスクリプティング言語であり、オブジェクト指向に基づいています。しかし、いくつかの重要な違いがあります。Rubyは動的型付け言語で全てのインスタンスがオブジェクトですが、Pikeは静的型付け言語で一部のインスタンスはプリミティブです。
歴史
[編集]- 1994年
- Fredrik HübinetteとPer Hedborを含むLysatorのプログラマーたちが、MUD向けに開発された言語であるLPCからPikeの前身であるLPC4を分離し、これをさまざまなアプリケーションのための迅速なプロトタイピング言語として使用します。
- 1994年
- LPCのライセンスが商業目的での使用を許可していないため、新たにGPL実装のµLPC(マイクロLPC)が開発されます。
- 1996年
- µLPCは商業的に有望な名前を提供するためにPikeに改名されます。
- 1996年
- Pikeの開発はRoxen Internet Softwareに引き継がれ、同社のリソースが提供されます。同社はPikeで開発されたWebサーバーであるRoxenを提供します。
- 2002年
- リンシェーピング大学のプログラミング環境研究室が、RoxenからPikeのメンテナンスを引き継ぎます。Opera Softwareのリンシェーピングオフィスでは、PikeプログラマーがOpera Miniアプリケーションのサーバー/ゲートウェイ部分で中心的な役割を果たすために、数名が雇用されました。
インストール
[編集]Pikeのダウンロードとインストール
[編集]Pikeは、UNIX、Linux、Windows、macOSなどの多くのプラットフォームで利用可能です。 Pikeの最新バージョンをダウンロードするには、Pikeの公式ウェブサイト( http://pike.lysator.liu.se/ )を訪問してください。
Linuxユーザーの場合、Pikeは多くのディストリビューションのパッケージリポジトリに含まれています。例えば、DebianやUbuntuの場合、以下のコマンドを使用してPikeをインストールすることができます。
$ sudo apt-get update $ sudo apt-get install pike8.0
Windowsユーザーの場合、PikeのWindowsバージョンをダウンロードしてインストールする必要があります。 ダウンロードしたファイルを実行して、ウィザードに従ってインストールしてください。
macOSユーザーの場合、Homebrewなどのパッケージマネージャーを使用してPikeをインストールすることができます。
ソースコードからのビルド
[編集]Pikeは、ビルド済みバイナリの他にソースコードも公開されているので、ソースコードからビルドしてインストールすることも可能です。
ここで問題になるのが、現在配布されているソースコードからビルドする過程で pike
自身が使われている点です。
このため、FreeBSDの様なビルド済みバイナリが配布されていないプラットホームでは、ビルドそのものが出来ません。
これを解決するには、ビルドにpike
自身が使われていなかった頃の過去のバージョン(例えば、0.6)をビルドしインストールし、その後、過去のバージョンのpike
を使って新しいバージョン(例えば、8.0)をインストールする二段階のビルドプロセスが有効です。
この方法で、FreeBSD 13.1/amd64 に Pike v8.0 release 1738 をインストールできました。
Pikeの実行
[編集]Pikeをインストールした後は、まずバージョンを確認します。以下のコマンドを使用してPikeのバージョン確認してください。
$ pike --dumpversion 8.0.1738
コマンドラインからPikeを起動することができます。以下のコマンドを使用してPikeを起動してください。
$ pike Pike v8.0 release 1738 running Hilfe v3.5 (Incremental Pike Frontend) > write("Hello world!); Hilfe Error: Unterminated string "\"Hello world!);\n" at line 1 > write("Hello world!"); Hello world!(1) Result: 12 > quit Exiting. $ _
Pikeの対話型シェルが起動し、コードを実行することができます。
- quit コマンドで終了し、コマンドラインに戻ります。
また、Pikeで書かれたスクリプトファイルを実行する場合は、以下のようにファイル名を指定して実行してください。
$ cat hello.pike int main() { write("Hello world!\n"); return 0; } $ pike hello.pike Hello world!
以上のように、Pikeをダウンロードしてインストールし、コマンドラインから起動することができます。
Pikeのオンライン実行環境
[編集]Pikeのオンライン実行環境は、ウェブ上でPikeプログラミング言語のコードを書き、実行できるオンラインツールです。これにより、ユーザーはPikeの構文や機能を試したり、小規模なプログラムを作成したりすることができます。
- 代表的なPikeのオンライン実行環境
- tutorialspoint.com
一般的に、オンライン実行環境は以下の機能を提供します:
- コードの入力エディタ
- ユーザーはPikeのコードを直接入力できるテキストエディタが提供されます。
- 実行ボタン
- ユーザーは入力したコードを実行するためのボタンが提供されます。
- 実行結果の表示
- コードの実行結果や出力が、ウェブページ上で表示されます。
- エラーメッセージの表示
- もしコードにエラーがある場合、エラーメッセージが表示されて、問題点を特定できます。
これにより、Pikeをインストールする必要なく、ウェブブラウザを介してPikeのコードを試したり学習したりすることができます。
Hello world
[編集]前の節で実行したプログラムを解説しましょう。
- hello.pike
int main() { write("Hello world!\n"); return 0; }
int main()
: この行は、main
関数を宣言しています。C言語と同様に、Pikeのプログラムは、main
関数から実行が開始されます。int
は、main
関数が整数を返すことを示します。{
と}
: これらは、main
関数の本体を定義するために使用されます。write("Hello world!\n");
: この行は、標準出力に "Hello world!" という文字列を出力するwrite
関数を呼び出しています。\n
は改行文字を表します。return 0;
: この行は、main
関数からプログラムを終了するために、整数値0
を返しています。
したがって、このプログラムの出力は、"Hello world!" という文字列に改行文字が続いたものになります。
変数と型
[編集]Pikeはインタープリタ言語であるため、変数に宣言が必要ないと思われるかもしれません。 しかし、Pikeでは変数宣言が必須であり、変数の型がコンパイル時に決定されるため、型安定性が高くなります。
変数の宣言
[編集]変数を宣言する場合、以下のように宣言します。
型名 変数名 = 初期値;
たとえば、整数型の変数xを宣言し、初期値に42を代入する場合、次のようになります。
int x = 42;
Pikeの変数宣言では、型名を省略できませんが、初期値を代入しない場合には、次のように変数宣言をすることができます。
int x;
データ型の基本
[編集]Pikeにはいくつかの基本的なデータ型があります。以下にそれらを説明します。
- 整数型(int): 整数を表現するためのデータ型です。例えば、
17
や-42
などの整数値を表現することができます。 - 浮動小数点型(float): 実数を表現するためのデータ型です。整数型とは異なり、小数点以下の部分を持つ数値を表現することができます。例えば、
3.14
や-0.5
などの実数値を表現することができます。 - 文字列型(string): 文字列を表現するためのデータ型です。文字列は文字の連続した列です。例えば、
"Hello"
や"Pike programming"
などの文字列を表現することができます。文字列はダブルクォーテーションで囲みます。 - 任意の型(mixed): 任意のデータ型を表現するための特殊なデータ型です。
mixed
型の変数はどのような型の値でも格納することができます。異なる型の値を組み合わせたり、柔軟なデータ構造を表現するために使用されます。
これらの基本的なデータ型は、Pikeプログラムで変数を宣言する際に使用されます。例えば、int number_of_monkeys;
は整数型の変数number_of_monkeys
を宣言しています。
また、基本的なデータ型の他にもコンテナ型と呼ばれるデータ型があります。コンテナ型は他のデータを格納するためのデータ型であり、配列(array)、マップ(mapping)、マルチセット(multiset)などがあります。これらのコンテナ型は複雑なデータ構造を扱うために使用されますが、基本的なデータ型と同様に変数を宣言して使用することができます。
- types.pike
int main() { // 基本型 // int i = 42; // 整数型の変数 float f = -16.2; // 浮動小数点型の変数 string s = "Hello"; // 文字列型の変数 mixed x = 100; // 任意の型の変数(mixed型) int | float n = 0; // 整数型か浮動小数点型の変数 write("i = %O, f = %O, s = %O, x = %O, n = %O\n", i, f, s, x, n); // コンテナ型 // // 配列(Array) array(string) fruits = ({ "apple", "banana", "orange" }); write("%O\n", fruits); // マッピング(mapping) mapping map = ([ "key1": "value1", "key2": "value2", "key3": "value3" ]); write("%O\n", map); // マルチセット(multiset) multiset mset = (< 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 6 >); write("%O\n", mset); return 0; }
- 実行結果
i = 42, f = -16.2, s = "Hello", x = 100, n = 0 ({ /* 3 elements */ "apple", "banana", "orange" }) ([ /* 3 elements */ "key1": "value1", "key2": "value2", "key3": "value3" ]) (< /* 11 elements */ 1, 1, 2, 3, 3, 4, 5, 5, 6, 6, 9 >)
まず、基本型の変数が宣言されています。以下のような変数があります:
int
: 整数型の変数で、i
という名前で宣言されており、値は42です。float
: 浮動小数点型の変数で、f
という名前で宣言されており、値は-16.2です。string
: 文字列型の変数で、s
という名前で宣言されており、「Hello」という文字列が格納されています。mixed
: 任意の型の値を格納できる変数で、x
という名前で宣言されており、値は100です。Pikeでは、mixed
型は異なる型の値を格納できる特殊な型です。int | float
: 整数型または浮動小数点型の値を格納できる変数で、n
という名前で宣言されており、初期値は0です。このような変数は、Pikeの型ユニオンを使用して宣言されています。
次に、コンテナ型の使用例が示されています。
array(string)
: 文字列の配列を表す変数で、fruits
という名前で宣言されています。この配列には、"apple"、"banana"、"orange"という3つの文字列が格納されています。mapping
: キーと値のペアのマッピング(連想配列)を表す変数で、map
という名前で宣言されています。このマッピングには、"key1"、"key2"、"key3"という3つのキーと、それぞれに対応する"value1"、"value2"、"value3"という値が含まれています。multiset
: 複数の要素を保持するマルチセットを表す変数で、mset
という名前で宣言されています。このマルチセットには、3、1、4、1、5、9、2、6、5、3、6という11個の要素が格納されています。マルチセットは、順序を持たずに要素を格納するデータ構造です。
未初期化の変数の値
[編集]Pikeでは変数は必ず宣言が必要なことは既に述べましたが、初期化は必須ではありませんでした。
では未初期化の値を参照するとどうなるでしょう?
- — 答えは、整数の0が返るです。
- uninitialized.pike
int main() { int i; write("Variable i has type %t and value %O\n", i, i); // => Variable i has type int and value 0 float f; write("Variable f has type %t and value %O\n", f, f); // => Variable f has type int and value 0 string s; write("Variable s has type %t and value %O\n", s, s); // => Variable s has type int and value 0 array a; write("Variable a has type %t and value %O\n", a, a); // => Variable a has type int and value 0 return 0; }
- 実行結果
Variable i has type int and value 0 Variable f has type int and value 0 Variable s has type int and value 0 Variable a has type int and value 0
整数型変数iの未初期化の値が0である点はともかく、他の型の変数も全て整数型の0が入っています。 未初期化の場合、でたらめな値が入ることは問題ですが、それっぽい値が入っていると初期化忘れを発見することが遅れます。 整数の0は比較的容易に検査できるため、初期化の不備を簡単に発見できます。 しかしながら、初期化を必ず行う習慣を身につけることが最も重要です。
またpike-9.x(開発バージョン)では、新しい型名 auto の導入が予定され、型推論を行うことが可能になります。 型推論には初期化が必須なので、初期化忘れが減る事も期待できます。
列挙型
[編集]Pikeにおける列挙型はenum
というキーワードを用いて定義されます。以下は、簡単な色の列挙型を定義し、それを使用して文字列を出力するプログラムの例です。
- enum.pike
enum Color { RED, BLUE, GREEN } int main() { Color color = BLUE; switch(color) { case RED: write("The color is red.\n"); break; case BLUE: write("The color is blue.\n"); break; case GREEN: write("The color is green.\n"); break; default: write("Unknown color.\n"); break; } return 0; }
- 実行結果
The color is blue.
上記のプログラムでは、Color
という列挙型を定義し、3つの定数 RED
、BLUE
、GREEN
を定義しています。その後、main()
関数内でColor
型の変数color
を宣言し、BLUE
で初期化しています。switch
文を使ってcolor
の値によって分岐し、該当する文字列を出力しています。
enum は、Pike 7.2.30 で追加されました。
演算子
[編集]Pikeにおいて、いくつかの関数はコード中で1文字または2文字で呼び出すことができ、コードを短くすることができます。 これらの関数は演算子と呼ばれ、既にたくさんの例でその動作を見てきました。
演算子は機能に基づいてカテゴリーに分類されますが、注意が必要です。なぜなら、一部の演算子には、それらが属するカテゴリーの範囲を超えた意味があるからです。
以下は整数に関する演算子の例です。
int main() { // 算術演算子 int a = 10, b = 5; int c = a + b; // 加算 int d = a - b; // 減算 int e = a * b; // 乗算 int f = a / b; // 除算 int g = a % b; // 剰余 // 比較演算子 int h = 5, i = 10; bool j = (h == i); // 等しい bool k = (h != i); // 等しくない bool l = (h < i); // より小さい bool m = (h > i); // より大きい bool n = (h <= i); // 以下 bool o = (h >= i); // 以上 // 論理演算子 bool p = 1, q = 0; bool r = (p && q); // 論理積(AND) bool s = (p || q); // 論理和(OR) bool t = (!q); // 否定(NOT) // 代入演算子 int u = 5; u += 10; // 加算して代入 u -= 5; // 減算して代入 u *= 2; // 乗算して代入 u /= 4; // 除算して代入 u %= 3; // 剰余して代入 // ビット演算子 int v = 0b101010, w = 0b110011; int x = (v & w); // 論理積(AND) int y = (v | w); // 論理和(OR) int z = (v ^ w); // 排他的論理和(XOR) int aa = (~v); // 反転(NOT) // シフト演算子 int bb = 0b101010; int cc = (bb << 2); // 左シフト int dd = (bb >> 2); // 右シフト // 条件演算子(三項演算子) int ee = (h > 5) ? 10 : 20; // その他の演算子 int ii = 5; ii++; // インクリメント ii--; // デクリメント int jj = (ii > 5) ? 10 : 20; // 条件演算子(三項演算子) return 0; }
- これらの演算子は、条件式やループ制御などでよく使用されます。
- boolはintの別名で、0が偽・0以外が真になります。
以下は文字列に関する演算子の例です。
int main() { string s1 = "Hello"; string s2 = "World"; // 文字列結合演算子 write("%O + %O => %O\n", s1, s2, s1 + s2); // => "HelloWorld" // 部分文字列演算子 write("%O[0..2] => %O\n", s1, s1[0..2]); // => "Hello"[0..2] => "Hel" // == (等しい) write("%O == %O is %d\n", s1, s2, s1 == s2); // => "Hello == World is 0" // != (等しくない) write("%O != %O is %d\n", s1, s2, s1 != s2); // => "Hello != World is 1" // > (より大きい) write("%O > %O is %d\n", s1, s2, s1 > s2); // => "Hello > World is 0" // >= (以上) write("%O >= %O is %d\n", s1, s2, s1 >= s2); // => "Hello >= World is 0" // < write("%O < %O is %d\n", s1, s2, s1 < s2); // => "Hello < World is 1" // <= (以下) write("%O <= %O is %d\n", s1, s2, s1 <= s2); // => "Hello <= World is 1" return 0; }
Pikeの演算子の中には、数学的な意味から離れた定義のものもあります。
int main() { string str = "The quick brown fox jumps over the lazy dog"; array(string) ary = str / " "; // => ({ "The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog" }) string s2 = ary * ", "; write("%O\n", s2); // => "The, quick, brown, fox, jumps, over, the, lazy, dog" return 0; }
このプログラムでは、Pikeの文字列の/
演算子と、配列の*
演算子を使っています。
まず、文字列 str
を "/"
(スペース) で分割して、array(string)
型の変数 ary
に代入しています。
この演算により、ary
には str
を単語ごとに分割した文字列の配列が格納されます。
例えば、ary[0]
には "The"
が格納されます。
この動作は、多くのスクリプティング言語のsplit()
関数に相当します。
次に、ary
の各要素を ", "
で結合して、新しい文字列 s2
を作成しています。
この演算により、s2
には ary
の各要素が ", "
で結合された文字列が格納されます。
例えば、s2
には "The, quick, brown, fox, jumps, over, the, lazy, dog"
が格納されます。
この動作は、多くのスクリプティング言語のjoin()
関数に相当します。
最後に、swrite
関数を使って、s2
の内容を表示しています。
write
関数はsprintf
関数の機能を含んでおり、指定したフォーマットに従って、文字列を生成する関数です。
"%O\n"
は、文字列を出力するフォーマット文字列であり、%O
は、Pike のオブジェクトを文字列に変換するための変換タイプです。
Pikeでは演算子も関数
[編集]「Pikeでは演算子も関数」とは、Pikeにおいて演算子 (たとえば +
, -
, *
, /
など) が単なる記号ではなく、それぞれが対応する関数として定義されているということを指します。
例えば、+
演算子は、2つの数値を足し合わせるために使われますが、実際には `+
関数として定義されています。
以下のように `+
関数を使って足し算を行うことができます。
int main() { int a = 1; int b = 2; int c = `+(a, b); // a + b と同じ write("`+(%d, %d) -> %d\n", a, b, c); // => `+(1, 2) -> 3 return 0; }
また、*
演算子も同様に `*
関数として定義されています。以下のように、文字列に対して *
関数を使って、指定した回数だけ繰り返した新しい文字列を作ることができます。
int main() { string str = "hello"; string new_str = `*(str,3); // str * 3 と同じ write("%O\n", new_str); // => "hellohellohello" return 0; }
このように、Pikeでは演算子も単なる記号ではなく、それぞれが対応する関数として定義されています。
このような関数の性質をもつ演算子の型を調べてみましょう。
int main() { write("%t\n", `+); // => function write("%t\n", main); // => function return 0; }
- `+ の型は、main と同じ function だとわかります。
- write の %t は、Pike固有の変換タイプで、引数の型を表示します。
int main() { function f = `+; write("f = `+\n"); write("f(%d, %d) -> %d\n", 3, 4, f(3, 4)); // => f(3, 4) -> 7 f = lambda(int x, int y) { int result = 1; for (int i = 0; i < y; i++) result *= x; return result; }; write("f(%d, %d) -> %d\n", 3, 4, f(3, 4)); // => f(3, 4) -> 81 return 0; }
このコードは、Pikeで関数を定義して呼び出す方法を示しています。
最初に、`+
関数を f という変数に代入しています。ここで + は、加算演算子 "+" に対応する関数です。その後、f に渡す引数を指定して、f 関数を呼び出しています。
次に、f に別の関数を代入しています。ここでは、ラムダ式を使って、与えられた2つの引数を掛け合わせる関数を定義しています。その後、f 関数を再度呼び出して、新しい関数が正しく動作していることを確認しています。
Pikeでは、関数を変数に代入することができるため、動的に関数を切り替えることができます。この機能を使うことで、プログラムの柔軟性を高めることができます。
ラムダ関数
[編集]Pikeにおけるラムダ関数とは、無名の関数を定義するための方法の一つです。ラムダ関数を定義するには、lambda
キーワードを使います。
例えば、以下のような lambda
関数を定義してみましょう。
function square = lambda(int x) { return x * x; }; write("square(%d) -> %d\n", 3, square(3)); // => square(3) -> 9
このコードでは、lambda
キーワードを使って、引数として整数 x
を受け取り、x * x
を返す square
関数を定義しています。
その後、square(3)
のようにして、引数として整数 3
を渡して square
関数を呼び出しています。
この呼び出しによって、9
が返されます。
Automap
[編集]配列の要素ごとに操作を行うために、map()には便利な構文があります。この構文を使用することで、コードをより読みやすくすることができます。
int main() { array a = ({1,3,5,7,9}), b = ({2,4,6,8,10}), c; c = a[*] + b[*]; write("c ->\n"); write("\t%O\n", c[*]); //=> c -> //=> 3 //=> 7 //=> 11 //=> 15 //=> 19 c[*] += 10; write("\nc = %O\n", c); //=> c = ({ /* 5 elements */ //=> 13, //=> 17, //=> 21, //=> 25, //=> 29 //=> }) return 0; }
- このプログラムでは、3つの配列a、b、cを使用しています。
- 配列aには1、3、5、7、9の要素が格納されています。
- 配列bには2、4、6、8、10の要素が格納されています。
- 配列cは一時的な配列で、結果を格納するために使用されます。
- 次の行では、automapという構文を使用して、配列aと配列bの要素ごとの和を計算し、結果を配列cに代入しています。
c = a[*] + b[*];
- 配列cの要素数全てに対し順に関数を適用します。
write("\t%O\n", c[*]);
- 次に、配列cの全ての要素に10を加算しています。
- 最後に、更新された配列cを%O構文で表示しています。
定数
[編集]定数は、プログラミングにおいて特定の値に名前を付けるために使われるもので、一度定義されたらその値を変更することができません。
構文は以下のようです。
constant 識別子 = 初期値 ;
Pike言語では、定数は初期値からの型推論を行います。つまり、定数の型は初期値に基づいて自動的に決定されます。変数とは異なり、明示的な型宣言は必要ありません。
sprinf() の書式文字列
[編集]Pikeのsprintf()
は、文字列をフォーマットするための関数です。
write()
もsprintf()
と同じ書式文字列を使ってフォーマット出来ます。
int main() { // 数値のフォーマット write("%d, %f\n", 123, 3.1415926536); //=> 123, 3.142 // 文字列のフォーマット write("Hello, %s!\n", "Alice"); //=> Hello, Alice! // 文字幅の指定(左詰め) write("|%-10sWorld|\n", "Hello"); //=> |Hello World| // 文字幅の指定(中央揃え) write("|%|10sWorld|\n", "Hello"); //=> | Hello World| // 文字幅の指定(右詰め) write("|%10sWorld|\n", "Hello"); //=> | HelloWorld| // 符号と修飾子の使用 int n = 123456; write("%+d, %0*d\n", n, 8, n); //=> +123456, 00123456 // 配列 write("%O\n", ({2,3,5,7,11})); // ({ /* 5 elements */ // 2, // 3, // 5, // 7, // 11 // }) return 0; }
このプログラムは、Pikeでwrite
関数を使用して、数値、文字列、および整数のフォーマットを行う方法を示しています。
最初の例では、%d
と%f
を使用して、整数と浮動小数点数をフォーマットしています。
%f
では、小数点以下4桁まで表示するように指定されています。
次の例では、%s
を使用して、文字列をフォーマットしています。
write
関数を使用することで、埋め込む文字列を動的に変更することができます。
次に、文字幅の指定について説明します。文字幅を指定するには、%s
の前に%10s
のように数字を入れます。
これは、表示する文字列が占める幅を指定するものです。
また、%-10s
のように-
を付けると、左揃えになります。
%10s
では右揃えになりますが、指定した文字列が占める幅よりも長い場合は、文字列の右端から切り捨てられます。
%|10s
では中央揃えになります。
最後に、符号と修飾子の使用について説明します。%+d
を使用すると、符号を付けて整数をフォーマットすることができます。
%0*d
を使用すると、整数を指定した桁数になるように0でパディングして表示することができます。
それぞれのwrite
関数の使用例を実行した際に得られる出力は、コメントの後に示されています。
変換タイプと修飾子
[編集]sprintf()
関数は、書式文字列という特殊な文字列を受け取り、それを解釈して文字列を作成します。
書式文字列は、出力する文字列内で特定の値を置き換えるために使用されます。書式文字列は、%
に続く文字で始まり、特定の形式で引数を受け取ることができます。たとえば、%d
は整数の書式文字列であり、%s
は文字列の書式文字列です。
書式文字列には、変換タイプ( conversion type )に修飾子( params )を追加することができます。修飾子は、書式文字列に対して動作を変更するために使用され、変換タイプは、出力の形式を制御するために使用されます。変換タイプと修飾子は、%
の後に続けて指定することができます。
変換タイプ 変換タイプ 説明 '%' パーセント 'b' 符号付きの2進数整数 'd' 符号付きの10進数整数 'u' 符号なしの10進数整数 'o' 符号付きの8進数整数 'x' 小文字の符号付き16進数整数 'X' 大文字の符号付き16進数整数 'c' 文字。フィールドサイズが指定されている場合、ネットワーク(ビッグエンディアン)バイトオーダーの整数の下位バイトを出力します。リトルエンディアンバイトオーダーを得るには、フィールドサイズを否定してください。 'f' 浮動小数点数(ロケール依存のフォーマット) 'g' 浮動小数点数の自動選択表現(ロケール依存のフォーマット) 'G' %gと同様ですが、指数部分を大文字の'E'で出力します。 'e' 指数表記の浮動小数点数(ロケール依存のフォーマット) 'E' %eと同様ですが、指数部分を大文字の'E'で出力します。 'F' バイナリIEEE表現の浮動小数点数(%4Fは単精度浮動小数点数、%8Fは倍精度浮動小数点数を出力します)。ネットワーク(ビッグエンディアン)バイトオーダーで出力されます。リトルエンディアンバイトオーダーを得るには、フィールドサイズを否定してください。 's' 文字列 'q' クォートされた文字列。制御文字、8ビットを超える文字、および引用符文字'\'および'"'をエスケープします。 'O' 任意の値。デバッグスタイルです。正確なフォーマットに依存しないでください。lfun::_sprintf()メソッドの実装者がデバッグに必要とする任意の出力形式を指定できます。 'p' オブジェクトのメモリアドレスの16進数表現。整数と浮動小数点数にはアドレスがなく、自身が出力されます。 'H' バイナリのホレリス文字列。 sprintf("%c%s", strlen(str), str) と等価です。引数(幅など)はフォーマットの長さ部分を調整します。8ビットの文字列が必要です。 'n' 引数なし。空の文字列を引数として "%s" と同じです。注意:修飾子 '@' がアクティブな場合は引数配列を受け取りますが、その内容は無視されます。 't' 引数の型。 '{'
'}'引数配列の各要素に対して、囲まれたフォーマットを実行します。
変換タイプは、省略できず必ず1つだけ指定します。
修飾子 修飾子 説明 '0' 数値の前に0を付けます(右揃えを意味します)。 '!' 切り捨てを切り替えます。 ' ' 正の整数の前にスペースを付けます。 '+' 正の整数の前にプラス記号を付けます。 '-' フィールド幅内で左揃え(デフォルトは右揃え)。 '|' フィールド幅内に中央揃えます。 '=' 文字列がフィールド幅より大きい場合、列モード(単語の間に改行を挿入し、スペースをスキップまたは追加することがあります)。 '/'と一緒に使用できません。 '/' 単語の間ではなくフィールド幅で改行し、列モードにします。 '='と一緒に使用できません。 '#' '\n'で区切られた単語のリストを表示するテーブルモード。上から下への順序です。 '$' 反転テーブルモード(左から右への順序)。 'n' (nは数字または*です)フィールド幅変換タイプ。 ':n' 小数点以下の桁数変換タイプ。 '.n' 小数点以下の桁数変換タイプ。 ';n' 列幅変換タイプ。 '*' nが*の場合、次の引数は精度/フィールドサイズに使用されます。引数は整数またはlfun::_sprintf()が受信した修飾マップである場合があります。 "precision" int "width" int(0..) "flag_left" bool "indent" int(0..) "'" パッド文字列を設定します。 'は(まだ)パッド文字列の一部にすることはできません。 '~' 引数リストからパッド文字列を取得します。 '<' 同じ引数を再度使用します。 '^' 生成された各行でこれを繰り返します。 '@' 引数配列の各要素に対してこのフォーマットを繰り返します。 '>' 文字列を列の下端に置きます(上端ではなく)。 '_' 幅をデータの長さに設定します
修飾子は省略できます。
制御構造
[編集]Pikeの制御構造は、プログラムの実行フローを制御するための構文や構造のことを指します。これらの制御構造には、条件分岐、ループ、反復などが含まれます。Pikeでは、以下のような主要な制御構造があります:
- 条件分岐
-
- if
- 条件に応じて実行するコードを選択します。条件が真の場合にはifブロックが実行され、偽の場合にはelseブロックが実行されます。
- switch
- 条件によって複数の選択肢の中から1つを選択します。式の結果に応じて、対応するcase文が実行されます。
- ループ
- 条件が満たされている間、あるいは特定の回数繰り返し処理を行います。
- while
- ループの前に条件をテストします。
- do-while
- ループの後に条件をテストします。
- for
- ループの開始前に初期化を行い、ループごとに条件をテストし、増加・減少などを行います。
- foreach
- foreach文は、配列やリストの各要素に対して一連の操作を実行します。foreach構文を使用することで、反復処理が簡潔に記述できます。
これらの制御構造を組み合わせて、複雑なプログラムの制御フローを構築することができます。 これにより、条件に基づいた処理や繰り返し処理を効率的に実装することができます。
条件分岐
[編集]条件分岐は、テスト条件を与えられ、実行するコードを選択する制御構造です。 これらは、バイナリの「実行するかしないか」から、入力パラメータに基づいて実行するコードを選択する大規模なコード表まで、さまざまです。
if
[編集]最も単純な条件分岐は、if文です。if文は、文が期待されるどこにでも書くことができ、次のように見えます。
if (条件式) 文1; else 文2; ;
カッコの後ろやelseの後ろにはセミコロンがないことに注意してください。if文の処理は次のように行われます。
- 最初に条件式を評価します。
- 評価結果がfalseの場合、4にジャンプします。
- 文1を実行します。
- 5にジャンプします。
- 文2を実行します。
- 処理が完了します。
要するに、条件式がtrueの場合は文1が実行され、それ以外の場合は文2が実行されます。 条件式がfalseの場合に何かを実行する必要がない場合は、else部分を全て省略することができます。
if ( 条件式 ) 文1;
逆に、条件式がfalseの場合に何かを評価する必要がない場合は、not演算子を使用して式のtrue/false値を反転する必要があります。
if ( ! 条件式 ) 文2;
ここにある文は、この章の残りの文と同様に、文のブロックである場合があります。ブロックとは、セミコロンで区切られた文のリストで、中括弧 { } で囲まれています。注意点として、ブロックの後ろにはセミコロンをつけないでください。上記の例は次のようになります。
if ( ! 条件式 ) { 文A; 文B; 文C; }
また、複数のif文を順番に配置して、最初の式がfalseである場合は、次の式、次の式などを続けて、最初のtrue式が見つかるまで続けることもできます。
if ( 式1 ) 文1; else if ( 式2 ) 文2; else if ( 式3 ) 文3; else 文4;
上記の例の特殊な場合は、各式で1つの変数を異なる値と比較する場合です。 そのようなアプリケーションでは、下記で説明するswitch文を使用してパフォーマンスを向上させ、コードを簡素化することができます。
switch
[編集]switch文は、条件に応じて複数の選択肢の中から1つを選択するための、より高度な制御構造です。以下のような形式になります。
switch (式) { case 定数1: 文1; break; case 定数2: 文2; break; case 定数3 .. 定数4: 文3; break; case 定数5: case 定数6: 文4; // Fallthrough default: 文5; }
switch文は、if文よりも少し複雑な構造をしていますが、まだかなり単純です。 まず、式を評価し、次のブロック内の全てのcase文を検索します。 式によって返された値と等しいものが見つかれば、Pikeはそのcase文の直後に続くコードを実行し続けます。 breakが出現すると、Pikeはスイッチブロックの残りのコードをスキップし、ブロックの後に続くコードを実行します。 次のcase文の前にbreakがなくても、必ずしも厳密に必要ではありません。 次のcase文の前にbreakがない場合、Pikeは単に実行を続け、そのcase文の直後のコードも実行します。
上記の例のcase文の1つは、範囲であるという点で異なります。 この場合、定数3と定数4の間の任意の値が文3にジャンプするようになります。 範囲は、定数3と定数4の値を含むことに注意してください。
- まとめ
int main() { int x = 3; int y = 5; // if文 if (x < y) { write("x is less than y\n"); } else if (x > y) { write("x is greater than y\n"); } else { write("x and y are equal\n"); } // switch文 switch (x) { case 1: write("x is 1\n"); break; case 2: write("x is 2\n"); break; case 3: write("x is 3\n"); break; default: write("x is not 1, 2, or 3\n"); break; } return 0; }
反復
[編集]反復は、コードを0回以上実行するために使用されます。これは、かなり異なる方法で実行できるため、4つの異なる反復制御構造があります。これらはすべて非常に似ているように見えるかもしれませんが、適切な時に適切なものを使用すると、コードは短く、シンプルになります。
while
[編集]whileは、ループ制御構造の中で最もシンプルなものです。elseの部分を除いたif文のように見えます。
while ( 式 ) 文;
動作方法の違いは大きくないため、式が真の場合に文が実行されます。 その後、式が再評価され、真であれば文が再度実行されます。 それから式を再度評価し、繰り返します... 以下は使用例です。
int e = 1; while (e < 10) { show_record(e); e = e + 1; }
これにより、show_recordが1、2、3、4の値で呼び出されます。
for
[編集]forは、単にwhileの拡張です。ループをより短く、コンパクトに書くための方法を提供します。構文は以下のようになります。
for ( 初期化文 ; 条件式 ; 加算式 ) 文 ;
forは、以下の手順を実行します。
- 初期化文を実行します。初期化文は、ループ変数を初期化するために一度だけ実行されます。
- 条件式を評価します
- 結果がfalseの場合、ループを終了し、ループ後にプログラムを続行します。
- 文を実行します。
- 加算式を実行します。
- 2から再開します。
これにより、whileセクションの例は次のように書くことができます。
for (int e = 1; e < 10; e = e + 1) show_record(e);
do-while
[編集]do-whileは、whileと似ていますが、必ず一度は文が実行されことが異なります。
do 文; while ( 式 ) ;
動作方法の違いは大きくないため、式が真の場合に文が実行されます。 その後、式が再評価され、真であれば文が再度実行されます。 それから式を再度評価し、繰り返します... 以下は使用例です。
int e = 100; do { show_record(e); e = e + 1; } while (e < 10);
これにより、show_recordが100で呼び出されます。
foreach(,)
[編集]foreachは、ループ内の各反復に対して明示的なテスト式を評価しない点でユニークです。 代わりに、foreachはセット内の各要素について1回だけ文を実行します。foreachには2つの構文があり、最初の構文は次のようになります。
foreach ( 配列式, ループ変数 ) 文;
foreachが行うことは以下の通りです。
- 配列式を評価します。これは配列を返さなければなりません。
- 配列が空であれば、ループを終了します。
- 次に、配列の最初の要素をループ変数に割り当てます。
- その後、文を実行します。
- 配列にさらに要素があれば、次の要素が変数に割り当てられ、それ以外の場合はループを終了します。
- 4に移動します。
foreach(;;)
[編集]Pike-7.4.10 から foreach に新しい構文が追加されました。
foreach ( 配列式; インデックス変数; ループ変数 ) 文;
// foreachループで配列を反復処理し、インデックスと値を表示する void example_foreach() { array numbers = ({2, 3, 5, 7, 11}); foreach (numbers; int index; int number) { write("Index: %d, Value: %d\n", index, number); } } // Array.Iteratorを使用して配列を反復処理し、インデックスと値を表示する void example_iterator() { array numbers = ({2, 3, 5, 7, 11}); Array.Iterator iterator = Array.Iterator(numbers); while (iterator) { int index = iterator->index(); int value = iterator->value(); write("Index: %d, Value: %d\n", index, value); iterator += 1; } } // Mapping.Iteratorを使用してマップを反復処理し、キーと値を表示する void example_mapping_iterator() { mapping data = ([ "a": 1, "b": 2, "c": 3 ]); Mapping.Iterator iterator = Mapping.Iterator(data); while (iterator) { mixed index = iterator->index(); mixed value = iterator->value(); write("Index: %O, Value: %O\n", index, value); iterator += 1; } } // foreach、Array.Iterator、Mapping.Iteratorを使用して配列とマップを反復処理する int main() { write("Using foreach:\n"); example_foreach(); write("\nUsing iterator on array:\n"); example_iterator(); write("\nUsing mapping iterator on mapping:\n"); example_mapping_iterator(); return 0; }
example_foreach
関数では、foreach
文を使用して配列numbers
の要素を反復処理しています。ループごとに、index
に現在のインデックスが、number
に対応する要素が割り当てられます。その後、index
とnumber
の値を表示します。example_iterator
関数では、Array.Iterator
を使用して配列numbers
のイテレータを作成し、while
ループを使用してイテレータを反復処理しています。イテレータが有効な間、現在のインデックスをindex
に取得し、対応する要素をvalue
に取得します。その後、index
とvalue
の値を表示します。example_mapping_iterator
関数では、マッピングdata
のイテレータを作成し、while
ループを使用してイテレータを反復処理しています。イテレータが有効な間、現在のインデックスをindex
に取得し、対応する値をvalue
に取得します。その後、index
とvalue
の値を表示します。main
関数では、上記の関数を使用して、foreach
文とイテレータの動作をデモンストレーションしています。それぞれのデモンストレーションの結果が表示されます。
ループからの脱出
[編集]上記のループ制御構文は、すべての問題を解決するのに十分ですが、すべての問題に簡単な解決策を提供するには不十分です。 ループの途中で抜け出すことができる機能が不足しています。これを行う方法は3つあります。
break
[編集]break文は、ループやswitch文の中で使用され、そのブロックから抜け出します。以下は、break文の構文と例です。
while (条件式) { // ループ処理 if (条件式) { break; } // ループ処理 }
条件式が真である限り、ループ処理を繰り返します。しかし、if文の条件式が真になった場合、break文が実行され、whileループから抜け出します。
continue
[編集]continue文は、ループ内で使用され、その時点での処理を中断し、次のループ処理を開始します。以下は、continue文の構文と例です。
while (条件式) { // ループ処理前半 if (条件式) { continue; } // ループ処理後半 }
条件式が真である限り、ループ処理を繰り返します。しかし、if文の条件式が真になった場合、continue文が実行され、ループの先頭に戻って、次のループ処理を開始します。
return
[編集]return文は、関数内で使用され、その時点での処理を終了し、関数から値を返します。以下は、return文の構文と例です。
int my_func(int x, int y) { if (x == y) { return 0; } // 関数処理 return x + y; }
my_func関数内で、xとyを受け取ります。if文の条件式が真の場合、0を返します。それ以外の場合は、xとyの合計値を返します。return文が実行されると、関数内の処理は終了し、関数から値が返されます。
- まとめ
int main() { // whileループ int i = 0; while (i < 10) { write(i + "\n"); i++; } // do-whileループ int j = 0; do { write(j + "\n"); j++; } while (j < 10); // for(;;)ループ for (int k = 0; k < 10; k++) { write(k + "\n"); } // foreach(,)ループ array fruits = ({ "apple", "banana", "cherry" }); foreach (fruits, string fruit) { write(fruit + "\n"); } // foreach(;;)ループ array numbers = ({2, 3, 5, 7, 11}); foreach (numbers; int index; int number) { write("Index: %d, Value: %d\n", index, number); } // break文 int l = 0; while (l < 10) { write(l + "\n"); l++; if (l == 5) { break; } } // continue文 int m = 0; while (m < 10) { m++; if (m == 5) { continue; } write(m + "\n"); } // return文 int n = my_func(3, 5); write(n + "\n"); return 0; } int my_func(int x, int y) { if (x == y) { return 0; } // 関数処理 return x + y; }
関数
[編集]関数は、Pikeプログラムの主要な構成要素の1つであり、複数の操作をまとめて実行するために使用されます。Pikeでの関数の基本的な概念は、他の多くのプログラミング言語と同様に、引数を受け取り、実行可能なコードブロックを定義し、戻り値を返すことです。
関数の定義と呼び出し
[編集]Pikeでは、関数の定義の構文は以下のようになります。
- 関数の定義
戻値型 関数名(引数リスト) { // 関数の処理 // return文で値を返す(必要な場合) }
引数リストは、「仮引数型 仮引数名」のペアを「,」で区切った0個以上のリストです。
関数の呼び出しの構文は以下のようになります。
- 関数の呼び出し
戻値型 変数 = 関数名(引数リスト);
引数リストは、実引数アを「,」で区切った0個以上のリストです。
ここでは、変数に戻値を保持していますが、式の中の項に関数を使ったり、そのまま他の関数の実引数にしたりすることも可能です。
int add2(int a, int b) { return a + b; } int main() { int n = add2(2, 3); write("n = %d\n", n); // => n = 5 return 0; }
上記のコードは、add2
という関数を定義し、その関数をmain
関数から呼び出しています。
add2
関数は、2つの整数 a
と b
を受け取り、それらの値を足して結果を返す関数です。
main
関数では、add2(2, 3)
を呼び出して、その結果を変数 n
に格納しています。この場合、引数として 2
と 3
を渡しているので、関数内の a
には 2
、b
には 3
が渡されます。結果として、add2(2, 3)
の返り値である 5
が変数 n
に格納されます。
最後に、write
関数を使って変数 n
の値を表示しています。出力結果は n = 5
となります。
この例では、関数 add2
を定義して再利用性を高め、main
関数からその関数を呼び出しています。関数を使うことで、同じ処理を繰り返し書く必要がなくなり、コードの保守性と可読性を向上させることができます。
引数の配列展開
[編集]Pikeプログラミングでは、引数の配列展開を行うことができます。
配列展開は、f( @({ 2, 3 }) )
のように配列に「@」を前置し、この場合はf( 2, 3 )
と同じ意味になります。
配列展開を使うと、配列を個々の引数として関数に渡すことができます。以下に例を示します。
int add2(int a, int b) { return a + b; } int main() { array(int) pair = ({ 2, 3 }); // 配列展開を使って関数に引数を渡す int n = add2(@pair); write("n = %d\n", n); // => n = 5 return 0; }
上記のコードは、add2
という関数に配列展開を使用して引数を渡し、その結果を変数n
に格納しています。
pair
というarray(int)
型の配列には2つの要素が含まれており、それぞれ2と3です。
add2(@pair)
の部分で、@
演算子を使用してpair
の配列展開を行っています。
これにより、add2
関数には2と3の2つの引数が個別に渡されます。
add2
関数では、引数a
とb
を受け取り、それらを足した結果を返しています。
その結果が変数n
に格納されます。
最後に、write
関数を使用して変数n
の値を表示しています。出力結果はn = 5
となります。
配列展開を使うことで、配列の要素を個別の引数として関数に渡すことができます。これにより、コードの記述を簡潔にし、柔軟性を高めることができます。
可変長引数
[編集]通常の関数は、引数の数は一定ですが、引数の数を可変にすることも出来ます。
- 可変長引数を持つ関数の定義
戻値型 関数名(仮引数型 ... 仮引数配列) { // 関数の処理 // return文で値を返す(必要な場合) }
仮引数配列は、配列で引数リストと同じ内容が入ります。
なお、配列展開は可変長引数を受け取る関数に対しても使用することができます。 可変長引数を持つ関数に配列を渡す場合も、配列展開を行って可変長引数として渡すことができます。
int sum(int ... rest) { int x = 0; foreach (rest, int n) { x+= n; } return x; } int main() { array(int) ary = ({ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); // 配列展開を使って可変長引数を渡す int n = sum(@ary); write("n = %d\n", n); // => n = 55 return 0; }
上記のコードは、可変長引数を持つsum
関数に配列展開を使用して引数を渡し、その結果を変数n
に格納しています。ary
というarray(int)
型の配列には1から10までの整数が含まれています。
sum(@ary)
の部分で、@
演算子を使用してary
の配列展開を行っています。これにより、sum
関数には1から10までの10個の引数が個別に渡されます。
sum
関数では、可変長引数rest
を受け取り、それらの引数を合計しています。foreach
ループを使用して、可変長引数rest
の各要素n
にアクセスし、それを変数x
に加算しています。
最後に、変数x
の値が変数n
に格納され、write
関数を使用して表示しています。出力結果はn = 55
となります。
この例では、可変長引数を持つ関数に配列展開を使用することで、配列の要素を個別の引数として関数に渡すことができます。可変長引数を使用することで、引数の数が可変的である場合でも柔軟に対応することができます。
再帰的呼び出し
[編集]Pikeでは、関数内で自己を呼び出す再帰的な関数を定義することができます。これにより、反復処理を使用せずに問題を解決できる場合があります。
たとえば、次のような再帰的な関数を定義できます。
int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } }
この関数は、与えられた整数nの階乗を計算します。再帰的呼び出しを使用して、nが0になるまで、nから1ずつ引いていきます。
たとえば、次のようにこの関数を呼び出すことができます。
int result = factorial(5);
この場合、resultには120が格納されます。
暗黙のラムダ
[編集]「暗黙のラムダ」は前処理および/または後処理が必要なコードを埋め込む便利な方法です。 関数呼び出しで始まり、直後にブレースブロックが続く文では、ブロックがラムダ関数に変換され、呼び出される関数の最後の引数として渡されます。
int times(int n, function block) { for (int i = 0; i < n; i++) block(); return n; } int main() { int x = 0; times(3) { write("Hello world! <%d>\n", x++); } ; // => Hello world! <0> // => Hello world! <1> // => Hello world! <2> return 0; }
times
という関数を定義して、その関数に引数として実行回数とブロックを渡し、ブロックを指定された回数分実行するという機能を持っています。
main
関数では、times
関数を呼び出し、第1引数に3
を、第2引数に{ write("Hello world! <%d>\n", x++); }
という暗黙のラムダを渡しています。この暗黙のラムダは、times
関数内でblock()
という形式で呼び出されます。block()
の中では、write
関数を用いて、"Hello world! <x>"というメッセージを表示し、x
の値をインクリメントしています。したがって、このプログラムは、"Hello world!"というメッセージと、それに続くx
の値を示すメッセージを3回表示します。
times
関数から制御が戻った後、main
関数は、write("Hello world! <%d>\n", x++);
によって、"Hello world! <3>"というメッセージを表示し、x
をインクリメントします。したがって、このプログラムは、合計で4回の"Hello world!"メッセージを表示し、x
の値は4になります。
このように、関数呼び出しに続くブロックが呼び出し先の関数の隠れた最後の関数引数となる例は、Ruby、Groovy、Kotlinなどに見ることが出来ます。
__ARGS__
[編集]開発中の Pike-9.0 では、__ARGS__を使って暗黙のラムダへ引数を渡すことが出来るようになります。
void times(int n, function block) { for (int i = 0; i < n; i++) { block(i); } } int main() { times(3) { [mixed x] = __ARGS__; write("Hello world! <%d>\n", x); }; // => Hello world! <0> // => Hello world! <1> // => Hello world! <2> return 0; }
void times(int n, function block)
: この関数は、引数として整数n
と関数block
を受け取ります。block
関数は整数を引数として受け取り、何らかの処理を行います。times
関数は、block
関数をn
回繰り返し実行します。int main()
: この関数はプログラムのエントリーポイントです。main()
関数の中にプログラムのメインの処理が記述されます。times(3) {...};
:times()
関数を呼び出しています。引数として3
を渡しており、block
関数の中身を記述しています。[mixed x] = __ARGS__;
:__ARGS__
はPikeの組み込み変数であり、関数の引数を表します。この行では、引数の値を変数x
に代入しています。mixed
は変数の型を表し、x
は変数名です。write("Hello world! <%d>\n", x);
:write()
関数は、引数として与えられた文字列を標準出力に書き出します。"Hello world! <%d>\n"
は書式付きの文字列であり、%d
は整数を表示するための書式指定子です。x
の値は%d
の位置に挿入されます。return 0;
:main()
関数の最後には、通常は終了コードを返すためのreturn
文があります。ここでは0
を返しています。
クロージャー
[編集]ラムダ式は外部の変数 x
をキャプチャし、クロージャーとして機能します。これにより、ラムダ式内で x
の値を変更することができます。
クロージャーは、関数内で定義された変数を含む関数オブジェクトであり、関数が終了してもその変数のスコープが維持されます。
この例では、times
関数内で定義された i
と main
関数内で定義された x
の2つの変数が存在し、ラムダ式が x
をキャプチャして変更しています。
これにより、ラムダ式が終了した後でも x
の値を保持できます。
- まとめ
// 関数定義 int add2(int a, int b) { return a + b; } // 可変長引数を持つ関数定義 int sum(int ... rest) { int x = 0; foreach (rest, int n) { x+= n; } return x; } // 再帰的呼び出し int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } } // 暗黙のラムダ int times(int n, function block) { for (int i = 0; i < n; i++) block(); return n; } int main() { // 関数の呼び出し int result = add2(2, 3); //=> 実行結果: result = 5 array(int) ary = ({32, 16}); result = add2(@ary); //=> 実行結果: result = 48 result = sum(1, 2, 3); //=> 実行結果: result = 6 ary = ({ 1, 2, 4, 8 }); result = sum(@ary); //=> 実行結果: result = 15 result = factorial(5); //=> 実行結果: result = 120 int x = 0; times(3) { write("Hello world! <%d>\n", x++); }; //=> 実行結果: //=> Hello world! <0> //=> Hello world! <1> //=> Hello world! <2> return 0; }
データ型
[編集]Pikeでは、型は2つの異なるコンテキストで使用されます。コンパイル時と実行時です。一部の型はコンパイル時のみ使用されます(void、mixed、およびすべての構造型)、その他のすべての型は実行時にも使用されます。
Pikeには、基本型とポインタ型の2つの種類の実行時データ型があります。基本型は変数に割り当てられたときにコピーされますが、ポインタ型は単にポインタがコピーされるため、同じものを指す2つの変数が得られます。ポインタ型は、array、mapping、multiset、program、object、およびfunctionの6つがあります。これらはすべてポインタ型であり、メモリ上の何かを指します。この「何か」は、それに対するポインタがもうない場合に解放されます。変数にポインタ型の値を割り当てる場合、この「何か」はコピーされません。代わりに、それに対する新しい参照が生成されます。これらの型のいずれかを関数の引数として渡す場合、関数が「何か」を変更する可能性があるため、特別な注意が必要です。この効果を望まない場合は、値を明示的にコピーする必要があります。後述します。
基本型
[編集]整数(int)
[編集]整数を使用する場合は、キーワードintを使用します。
int i = 42;
整数リテラル 正規表現 例 説明 /-?[1-9][0-9]*/ 42 十進数 /-?0[0-9]*/ 0116 八進数 /-?0[xX][0-9a-fA-F]+/ 0x4e 十六進数 /-?0[bB][01]+/ 0b1001110 二進数 /-?'\\?.'/ 'N' ASCII 文字
浮動小数点数(float)
[編集]浮動小数点数を使用する場合は、キーワードfloatを使用します。
float f = 3.1415926536;
浮動小数点リテラル 正規表現 例 等値な表現 /-?[0-9]*\.[0-9]+/ 3.1415926 3.1415926 /-?[0-9]+e-?[0-9]+/ -5e3 -5000.0 /-?[0-9]*\.[0-9]+e-?[0-9]+/ .22e-2 0.0022
文字列(string)
[編集]文字列を使用する場合は、キーワードstringを使用します。 文字列は、二重引用符で囲まれた文字列リテラルを使用して表します。
string s = "Hello, world!";
ポインタ型
[編集]この節では、ポインタ型について説明されています。基本型は非常に基本的な型であり、ほとんどのポインタ型は、基本型を格納する興味深い方法です。 ポインタ型には、配列、連想配列、マルチセット、プログラム、オブジェクト、関数があります。 これらはすべて、メモリ内の何かを指すポインタであり、ポインタがなくなると、その「何か」は解放されます。 ポインタ型の値を変数に代入すると、この「何か」がコピーされるのではなく、それに対する新しい参照が生成されます。 関数にこれらの型の値を引数として渡す場合には、特別な注意が必要であり、関数が「何か」を変更する可能性があるため、この効果が望ましくない場合には、明示的に値をコピーする必要があります。
- 配列 (array): 複数の値を格納するためのデータ構造で、添字を用いて要素にアクセスする。添字は整数でなければならない。例えば、a[0]、a[1]、a[2] などと表記される。
- 連想配列 (mapping): キーと値のペアを格納するためのデータ構造。添字に任意の型を使うことができる。例えば、m["key"] = value のように表記される。
- マルチセット (multiset): 値の集合を格納するためのデータ構造で、同じ値を複数格納することができる。集合に含まれる値は順序関係がなく、一意である必要はない。例えば、s += ({ 1, 2, 3 }) のように表記される。
- プログラム (program): Pikeのプログラムを表すための型。ファイルから読み込まれたり、インターネット上からダウンロードされたりする。例えば、p = read_file("program.pike") のように表記される。
- オブジェクト (object): Pikeのオブジェクトを表すための型。メソッドや変数を含み、複数のオブジェクトを作成することができる。例えば、o = create_object("MyObject") のように表記される。
- 関数 (function): Pikeの関数を表すための型。プログラムやオブジェクトに属することができ、変数に代入することができる。例えば、f = lambda( int x ) { return x * x; } のように表記される。
配列(array)
[編集]Pikeでは、配列は複数の要素を格納するためのデータ構造です。配列は、コンマで区切られた要素のリストで初期化することができます。
array a = ({2, 3, 7});
このコードでは、aという配列が初期化され、要素として2, 3, 7が格納されます。
配列の要素にアクセスするには、配列名に角括弧を使用して、インデックスを指定します。例えば、a[0]は配列aの最初の要素を表します。a[1]は2番目の要素、a[2]は3番目の要素を表します。
int first_element = a[0]; // 2を取得 int second_element = a[1]; // 3を取得 int third_element = a[2]; // 7を取得
配列の要素に値を代入するには、同様に角括弧を使用して、インデックスを指定し、代入する値を指定します。
a[0] = 4; // a[0]に4を代入
配列に要素を追加するには、配列の最後に新しい要素を追加する += 演算子を使用します。
a += ({ 3.14159 }); // aの末尾に 3.14159 を追加 a += ({ "abc" }); // aの末尾に "abc" を追加
foreach 文を使うと配列要素を順に処理を行うことが出来ます。
foreach(a, mixed e) { if (intp(e)) write("%d ", e); else if (floatp(e)) write("%f ", e); else if (stringp(e)) write("\"%s\" ", e); else write("Unknown type"); } write("\n");
foreach
文は、指定された配列の各要素を順番に取り出し、ループを実行するための制御構文です。このプログラムでは、foreach(a, mixed e)
という文で、配列aの各要素を取り出して、それをeに代入しています。mixed
は、Pikeのデータ型で、任意の型の値を受け入れることができます。
if
文は、eの型に応じて、適切な形式で表示するために、条件分岐を行います。intp(e)
、floatp(e)
、stringp(e)
は、それぞれeが整数、浮動小数点数、文字列であるかどうかを判定する関数です。eが整数であれば、"%d "
というフォーマット文字列を使用して、その整数を表示します。eが浮動小数点数であれば、"%f "
というフォーマット文字列を使用して、その浮動小数点数を表示します。eが文字列であれば、"\"%s\" "
というフォーマット文字列を使用して、その文字列をダブルクォーテーションで囲んで表示します。
最後のelse
節は、eがどの型にも該当しない場合に実行され、"Unknown type"という文字列を表示します。
[TODO:このコードは要素に配列を含む配列に対応していません。]
連想配列(mapping)
[編集]Pikeにおける連想配列は、キーと値のペアの集合を表現するデータ型であり、mapping
という名前で定義されています。
連想配列は、キーと値のペアを追加することができます。キーには文字列、整数、浮動小数点数、オブジェクトなどを使用することができ、値には任意のデータ型を格納することができます。
以下は、mapping
を使用したサンプルコードです。
mapping m = ([ "key1": "value1", 2: 3.14, ({1,2}): "value3"]); m["key2"] = "value2"; foreach ( m; mixed k; mixed v) write("%O: %O\n", k, v);
このコードでは、mapping
を使用して、キーと値のペアを格納するm
を宣言し、キーに文字列、整数、配列などを使用しています。また、m
に新しいキーと値のペアを追加するために、m["key2"] = "value2";
のように書いています。
foreach
文では、m
の各要素をk
とv
に代入して、write
関数を用いて、k
とv
を適切なフォーマットで文字列に変換し、write
関数で出力しています。
上記のプログラムは、以下のような出力を生成します。
"key2": "value2" 2: 3.14 ({ /* 2 elements */ 1, 2 }): "value3" "key1": "value1"
mapping
は、連想配列を実装するための主要なデータ型であり、Pikeの様々な機能に使用されます。
マルチセット(multiset)
[編集]Pikeにおけるマルチセットは、要素の重複を許した、要素の集合を表現するデータ型であり、multiset
という名前で定義されています。
マルチセットは、要素を追加することができ、同じ要素を複数回追加することもできます。
multiset m = (< 1, 2, 3, 3, "a", "b", "b" >); m["c"] = 55; m[3] = false; foreach (m; mixed k; mixed n) { write("%O: %O\n", k, m[n]); }
- 実行結果
1: 1 2: 1 3: 1 "c": 1 "a": 1 "b": 1 "b": 1
- このコードは、マルチセットを使った例です。最初に、< 1, 2, 3, 3, "a", "b", "b" >というマルチセットを作成し、変数mに代入しています。
- このマルチセットには、整数1、2、および3、文字列"a"と"b"が、それぞれ1回ずつ登録されています。これに加えて、インデックス"c"と値55が追加されています。また、インデックス3に対応する値は、falseに変更されています。
- 次に、foreachループを使用してマルチセットを反復処理し、各インデックスとその値を出力しています。このループでは、変数kに現在のインデックスが、変数nに現在のインデックスの値が設定されます。マルチセットには、同じインデックスを複数持たせることができるため、出力結果には、インデックス"b"が2回表示されています。
- write関数を使用して、各インデックスとその値を出力しています。%Oフォーマット指示子は、値の型に応じてその値を出力します。この場合、インデックスに対応する値は、1になります。
プログラム(program)
[編集]Pikeにおいて、プログラムとはC++におけるクラスに相当します。 プログラムは、そのプログラムに定義された関数や変数がどのように定義されているかの表を保持します。 また、コード自体、デバッグ情報、継承された他のプログラムへの参照も保持します。 ただし、プログラム自体にデータを格納するスペースはありません。 プログラム内の情報は、ファイルまたは文字列がPikeコンパイラを通過したときに収集されます。
次に議論するオブジェクトに格納するために必要な変数スペースは、オブジェクトに格納されます。
プログラムを書くことは簡単で、プログラムをメモリにロードするには、compile_fileやキャスト演算子を使うことができます。 キャスト演算子を使用する場合、プログラムはキャッシュされ、次回以降は同じプログラムが返されます。 また、classキーワードを使用すると、プログラムの中に別のプログラムを書くことができます。
データ領域を作成するには、プログラムをインスタンス化する必要があり、ポインタを使用して、新しいオブジェクトを作成し、そのオブジェクト内のcreate関数を呼び出すことで実現されます。 すべてのプログラムは文字列をコンパイルして生成されます。 このために、compile、compile_file、compile_stringという3つの関数があります。 また、キャスト演算子を使用することもできます。
==や!=を使用することで、2つのプログラムが同じかどうかを確認できます。 また、p[args]やp->identifierのような演算子や関数も重要です。
オブジェクト(object)
[編集]プログラムはデータを保存する場所を持たず、どのようにデータを保存するかを示しているだけであるため、データを保存するにはオブジェクトが必要です。 オブジェクトは、プログラムから複製されたメモリのチャンクであり、複数のオブジェクトを1つのプログラムから作成できます。 各オブジェクトには、独自の変数があり、そのオブジェクト内の関数がこれらの変数で操作を実行します。 オブジェクト指向プログラミングは非常に柔軟で、データの保存方法を変更しても、オブジェクトの外部に変更を加える必要はありません。
関数(function)
[編集]オブジェクトの文字列によるインデックス化で、文字列がオブジェクト内の関数名である場合、関数が返されます。 しかし、その名前にもかかわらず、関数は実際には関数ポインターです。関数ポインターは、他のデータ型と同じように渡すことができます。 関数ポインターは、関数名を引数として受け取り、その関数が指し示すオブジェクトと、その関数を実行するための関数ポインターを返す関数も存在します。 また、関数内で別の関数を定義するために lambda キーワードを使用することもできます。 Pike では関数のオーバーロードはできません。関数ポインターには、関数の呼び出しや関数名の取得、関数が定義されたオブジェクトの取得などの機能があります。
コンパイル時の型
[編集]コンパイル時の型には、「void」と「mixed」の2つの型があります。
「void」は、値の存在またはオプション性を示すために使用されます。関数の戻り値として使用されることが多く、関数が値を返さないことを示します。また、関数パラメータの型セットの1つとして使用されることがあり、その場合は、呼び出し元がそのパラメータを省略することができ、その場合は特殊な値UNDEFINEDにデフォルトで設定されます。
「mixed」は、任意の型の値を渡すことができ、実際のランタイムで使用される値の型が完全に不明であることを示すために使用されます。この型は、コンテナクラスを実装する場合(クラス内のコードで実際の値が操作されない場合)、または実際のコンパイル時の型が複雑になりすぎた場合の便宜的なフォールバックとして使用されます。
また、基本型をパラメーターで指定して基本型をサブタイプ化するか、タイプユニオン演算子(|)を使用して複数の代替型を指定することで、より特定のコンパイル時の型を構築することができます。ただし、実行時の型は宣言されたコンパイル時の型と異なる場合があるため、注意が必要です。
変数
[編集]変数を宣言する際には、変数がどの種類の変数であるかを指定する必要があります。intやstringなどの多くのタイプに対しては、非常に簡単に指定できます。しかし、より興味深い変数の宣言方法があります。
いくつかの例を見てみましょう。
int x; // xは整数です int|string x; // xは文字列または整数です array(string) x; // xは文字列の配列です array x; // xはmixedの配列です mixed x; // xはどのタイプでもかまいません string *x; // xは文字列の配列です // xはintからstringへのマッピングです mapping(string:int) x; // xはStdio.Fileを実装しています Stdio.File x; // xはStdio.Fileを実装しています object(Stdio.File) x; // xは2つの整数引数を取り、文字列を返す関数です function(int,int:string) x; // xは何個でもの整数引数を取り、何も返さない関数です。 function(int...:void) x; // xは...複雑です mapping(string:function(string|int...:mapping(string:array(string)))) x;
これらの例を見ると、型を指定する興味深い方法があることがわかります。
以下は可能なもののリストです。
- mixed
- これは、変数が任意のタイプを含んでいること、または関数が任意の値を返すことを意味します。
- array(type)
- これは、タイプtypeの要素の配列を意味します。
- mapping(key type
- value type)
- これは、キーがkey typeで値がvalue typeであるマッピングです。
- multiset(type)
- これは、タイプtypeの値を含むマルチセットを意味します。
- object(program)
- これは、指定されたプログラムを「実装」するオブジェクトを意味します。プログラムはクラス、定数、または文字列であることができます。プログラムが文字列である場合、最初にプログラムにキャストされます。このキャストについての詳細については、inheritのドキュメントを参照してください。コンパイラは、このオブジェクトでアクセスされるすべての関数または変数について、その関数または変数がプログラムで持つ型情報と同じであると仮定します。
- program
- これも「programを実装するオブジェクト」を意味します。programはクラスまたは定数であることができます。
プリプロセッサ
[編集]Pikeコードはコンパイラに送信される前に、プリプロセッサを経由します。 プリプロセッサは、ソースコードをPike内部表現に変換し、いくつかのシンプルな正規化と整合性チェックを実行し、プログラマがファイルに入力した「プリプロセッサディレクティブ」を実行します。 プリプロセッサディレクティブは、単純なコード生成と操作を許容する非常に単純なプログラミング言語のようなものです。 コードプリプロセッサは、Pike内でcpp呼び出しによって呼び出すことができます。
プリプロセッサは、ファイルの文字エンコーディングを決定する必要があるため、最初に2つのバイトを調べ、この表に従って解釈します。 バイト0とバイト1の組み合わせに対して、その他のバイトについては、#charsetディレクティブが見つかるまでiso-8859-1エンコーディングとして扱われます。 EBCDICで書かれたプログラムで#charsetディレクティブが省略されている場合はエラーになります。#charsetディレクティブをエンコードすることも可能です。
Byte 0 Byte 1 解釈 0 0 32bit ワイド文字列 0 >0 16bit Unicode 文字列 >0 0 16bit Unicode 文字列(逆バイトオーダ) 0xfe 0xff 16bit Unicode 文字列 0xff 0xfe 16bit Unicode 文字列(逆バイトオーダ) 0x7b 0x83 EBCDIC-US ("#c"). 0x7b 0x40 EBCDIC-US ("# "). 0x7b 0x09 EBCDIC-US ("#\t").
プリプロセッサは、文字列以外の連続した空白文字を改行以外は単一の空白文字に変換し、すべての//や/***/コメント、#!行を削除します。 PikeはANSI/DECエスケープシーケンスを空白として扱います。
- defineディレクティブは、マクロや定数を定義するために最も使用されるプリプロセッサの機能の1つです。
- ifdefおよび#ifndefで使用できる定義が存在することを示すことができます。
定義に特定の値を設定することもでき、この値はソースコード内の定義が置かれた場所に挿入されます。 マクロには最大254個の引数があります。
プリプロセッサ・ディレクティブ ディレクティブ 意味 #! シェルの統合を可能にするコメント(Pikeアプリケーションは、可能な限りクロスプラットフォーム互換性のために「#! /usr/bin/env pike」という行で始めることが推奨されている) #line このディレクティブの後の行の行番号を指定します。また、エラーメッセージにおいても、指定された行番号が使用されます。 #"" #" で始まる文字列リテラルの中で改行がある場合、それが文字列リテラルに含まれます。 #( )、#[ ]、#{ } #( で始まる文字列リテラルを作成します。このリテラル中にある全ての文字は、文字として扱われます。 #string 次に続くファイルを読み込み、その内容を文字列として挿入します。 #include 次に続くファイルの内容を、この場所に挿入します。 #if 式 式が true であれば、続くコードブロックが有効になります。#endif、#else、#elseif、#elif のいずれかが同じネストレベルで出現すると、コードブロックは終了します。 #ifdef 識別子 識別子がマクロであるかどうかを確認します。 #ifndef 識別子がマクロでないことを確認します。 #endif #if、#ifdef、#ifndef、#else、#elseif、#elif で開始されたブロックを終了します。 #else 現在のコードブロックを反転させ、もう一つのコードブロックに分割します。 #elif、#elseif #else と #if を組み合わせたもので、余分なネストレベルを追加しません。 #define <identifier>を<replacement string>に置換する。引数を用いることもできる。 #undef 指定されたマクロ定義を削除する。 #charset ファイルがエンコードされている文字セットを指定する。 #pike Pikeコンパイラの後方互換性レベルを設定する。 #pragma コンパイラのための汎用ディレクティブ。 #require 依存関係の失敗を検出する。 #warning プリプロセッサ中に警告を生成する。 #error プリプロセッサ中にエラーを生成する。
pragma のフラッグ(一部) all_inline このオプションは、後に続くすべての関数に inline
修飾子を追加することと同じです。all_final コンパイラに、すべてのシンボルを final
としてマークするよう指示します。deprecation_warnings 非推奨のシンボルの使用に対する警告を有効にします(デフォルト)。 no_deprecation_warnings 非推奨のシンボルの使用に対する警告を無効にします。これは通常、非推奨のシンボルを実装するコードで使用されます。 save_parent 必要ない場合でも、ネストされたクラスがその周囲のクラスへの参照を保存するようにします。 dont_save_parent "save_parent"
の反対です。グローバルシンボルpredef::pragma_save_parent
が設定されている場合、これをオーバーライドする必要があります。strict_types コンパイラが型が正しいかどうかを確信できないすべての場合に警告を有効にします。 disassemble コンパイルされるコードの逆アセンブル出力を有効にします。このオプションは、基本的には関数レベルのスコープを持つため、数行だけ有効にすると通常は無効です。これは、 Debug.assembler_debug()
のレベル3
に類似しています。no_disassemble 逆アセンブル出力を無効にします(デフォルト)。
定義済マクロ 定義済マクロ 意味 __VERSION__ float型として表された現在のPikeバージョンを含みます。別のPikeバージョンがエミュレートされた場合、この定義はそれに応じて更新されます。 __MAJOR__ 現在のPikeバージョンのメジャーパートを整数として表します。別のPikeバージョンがエミュレートされた場合、この定義はそれに応じて更新されます。 __MINOR__ 現在のPikeバージョンのマイナーパートを整数として表します。別のPikeバージョンがエミュレートされた場合、この定義はそれに応じて更新されます。 __BUILD__ 現在のPikeバージョンのビルド番号を整数として表します。別のPikeバージョンがエミュレートされた場合、この定数は変更されません。 __REAL_VERSION__ 常に、float型で表された現在のPikeバージョンを含みます。 __REAL_MAJOR__ 常に、現在のPikeバージョンのメジャーパートを整数として表します。 __REAL_MINOR__ 常に、現在のPikeバージョンのマイナーパートを整数として表します。 __REAL_BUILD__ 常に、現在のPikeバージョンのビルド番号を整数として表します。 __DATE__ コンパイル時の現在の日付、例えば "Jul 28 2001"を含みます。 __TIME__ コンパイル時の現在の時刻、例えば "12:20:51"を含みます。 __FILE__ ソースファイルのファイルパスと名前を含みます。 __DIR__ ソースファイルのディレクトリパスを含みます。 __LINE__ ソースファイル内の現在の行番号を整数として表します。 __COUNTER__ 独自のカウンタを含みます。整数で表され、一意である(展開された場合)。 __AUTO_BIGNUM__ 自動bignum変換が有効になっているときに定義されます。有効になっていると、整数が整数で表現できないほど大きくなったときに自動的にbignumに変換されます。これにより、プログラムがクラッシュする代わりに性能がわずかに低下します。Pike 8.0以降、この定義は常に設定されています。 __NT__ PikeがMicrosoft Windows OSで動作している場合に定義されます。Microsoft Windows NTという名前ですが、Microsoft Windows NTだけではありません。 __PIKE__ この定義は常にtrueです。 __amigaos__ この定義は、PikeがAmiga OSで実行されている場合に定義されます。 メソッド Pragma このマクロは、対応する#pragmadirectiveをソースに挿入します。例えば、_Pragma("strict_types")は、#pragma strict_typesと同じです。 static_assert この定義は、シンボル_Static_assertに展開されます。これは、静的(つまりコンパイル時)アサーションを実行するための推奨方法です。注意:このマクロは、静的アサーションがサポートされているかどうかをチェックするためにも使用できます。
オブジェクト指向プログラミング
[編集]Pikeはオブジェクト指向プログラミング(OOP)をサポートしており、PikeのOOP機能を使うことでプログラムのモジュール化や再利用性の向上が可能になります。
クラスとオブジェクトの概念
[編集]クラスはオブジェクトの設計図であり、オブジェクトはそのクラスの実体です。クラスは、そのクラスが持つ属性(フィールド)と操作(メソッド)を定義します。オブジェクトは、そのクラスが持つ属性の値と、そのクラスが定義する操作を実行することができます。
クラスの定義とインスタンス化
[編集]この例では、クラス定義の例として、人物のクラスが作成されています。クラス定義は、classキーワードで始まり、波括弧 { } で囲まれたメンバ変数とメソッドの定義を含みます。
- person.pike
class person { string name; int age; void create(string n, int a) { name = n; age = a; } void say_hello() { write("Hello, I'm " + name + ".\n"); } } int main() { person john = person("John", 30); john->say_hello(); write("%s is %d year(s) old.\n", john->name, john->age); return 0; }
クラスのメンバ変数は、各オブジェクトに1つずつ存在します。この例では、人物の名前を表す文字列型の変数nameと年齢を表す整数型の変数ageが定義されています。
メソッドは、クラスのオブジェクトに適用できる機能を記述します。この例では、say_helloというメソッドが定義されています。このメソッドは、"Hello, I'm 名前."というメッセージが表示します。
クラスのオブジェクトは、クラス名を呼び出し可能な関数のように使用されます。例えば、john->say_hello();は、johnオブジェクトのsay_helloメソッドを呼び出します。
この例では、createという特別なメソッドが定義されています。このメソッドは、オブジェクトをクローンする際に指定した引数を処理します。 このメソッドは、C++プログラマーがコンストラクタと呼ぶものに相当します。
この例では、クラスにdestroyというメソッドが定義されていませんが、このメソッドを定義することもできます。 このメソッドは、オブジェクトが消える直前に実行されるもので、C++プログラマーがデストラクタと呼ぶものに相当します。 ただし、Pikeは自動ガベージコレクションを備えているため、C++ほどは頻繁にデストラクタが必要になることはありません。
構造体としてのクラス
[編集]Pikeのクラスは、単なる構造体としても使用できます。例えば、上記のコードの
write("%s is %d year(s) old.\n", john->name, john->age);
john->name
, john->age
のようにインスタンス->メンバ変数
の構文でインスタンスのメンバ変数を参照できます。
次の例では、クラスの配列を作りメンバ変数をフリーハンドに扱っています。
- address_book.pike
class address_book_entry { string name; string address; string phone_number; } int main() { array(address_book_entry) address_book = ({ }); address_book_entry alice = address_book_entry(); alice->name = "Alice"; alice->address = "123 Main St, Anytown USA"; alice->phone_number = "555-1234"; address_book_entry bob = address_book_entry(); bob->name = "Bob"; bob->address = "456 Elm St, Anytown USA"; bob->phone_number = "555-5678"; address_book += ({ alice, bob }); foreach(address_book, address_book_entry entry) { write("Name: %s,\tAddress: %s,\tPhone Number: %s\n", entry->name, entry->address, entry->phone_number); } return 0; }
このコードは、住所録の項目を表すための address_book_entry
というクラスを定義し、そのクラスを使用して住所録を作成しています。
まず、address_book_entry
クラスは、名前、住所、電話番号の3つのメンバ変数を持っています。
そして、main
関数では、まず空の住所録を表す配列 address_book
を作成します。
次に、address_book_entry
クラスから2つのエントリー alice
と bob
を作成し、それぞれの名前、住所、電話番号を設定します。
そして、これらのエントリーを address_book
配列に追加します。
最後に、foreach
ループを使用して、address_book
配列に含まれるすべてのエントリーを出力します。
各エントリーの情報は、write
関数を使用して整形され出力されます。
このコードを実行すると、以下のような出力が得られます:
Name: Alice, Address: 123 Main St, Anytown USA, Phone Number: 555-1234 Name: Bob, Address: 456 Elm St, Anytown USA, Phone Number: 555-5678
これは、2つのエントリーが正常に作成され、住所録に追加され、正常に出力されていることを示しています。
プログラムはクラスでクラスはプログラム
[編集]Pikeにおいて、プログラムとクラスは同じであることが驚くかもしれません。つまり、Pikeではプログラムとクラスは同義語として使用されることがあります。
ファイル内のPikeプログラムはクラス定義であり、ファイルで定義されたメソッドはそのクラスのメソッドであり、グローバル変数(つまり、メソッドの外で定義された変数)はメンバ変数です。必要であれば、ファイル内のコードに暗黙的に「class {}」があると想像できます。
ただし、細かいクラスごとにそれぞれのファイルを用意しなければならないとなると面倒になるので、「class {}」の表記も用意されています。
- person.pike
string name; int age; void create(string n, int a) { name = n; age = a; } void say_hello() { write("Hello, I'm " + name + ".\n"); }
見覚えがあるコードですが、class { } で囲まれていませんね。
Pikeのクラスとプログラムの関係は特異的ですが、類例としてはZigの構造体とプログラムの関係があります。
複素数型を実装してみた
[編集]複素数は実数部と虚数部から成る数の形式ですが、Pikeには組み込みの複素数型がないため、独自のクラスを作成しました。
complex
クラスは、実数部と虚数部をメンバ変数として持ち、加算、減算、乗算などの操作を行う演算子を提供しています。また、絶対値や文字列化も実装されています。
- complex.pike
class complex { private float _real = 0.0; private float _imag = 0.0; float `real() { return _real; } float `imag() { return _imag; } void create(mixed real, mixed ... rest) { this::_real = (float) real; this::_imag = sizeof(rest) ? (float)rest[0] : 0.0; } // 加算メソッド complex `+(complex other) { return complex(real + other.real, imag + other.imag); } mixed ``+(mixed arg) { return complex((float)arg + real, imag); } // 減算メソッド complex `-(complex other) { return complex(real - other.real, imag - other.imag); } mixed ``-(mixed arg) { return complex((float)arg - real, -imag); } // 乗算メソッド complex `*(complex other) { float r = real * other.real - imag * other.imag; float i = real * other.imag + imag * other.real; return complex(r, i); } mixed ``*(mixed arg) { arg = (float)arg; return complex(arg * real, arg * imag); } // 絶対値 float `abs() { return sqrt(real * real + imag * imag); } // 文字列化 string _sprintf() { if (imag == 0.0) return sprintf("%O", real); if (real == 0.0) return sprintf("%Oi", imag); if (imag < 0.0) return sprintf("(%O - %Oi)", real, -imag); return sprintf("(%O + %Oi)", real, imag); } } int main() { complex c1 = complex(3.0, 4.0); complex c2 = complex(1.5, -1.0); write("complex(999.0, 0.0) = %O\n", complex(999.0, 0.0)); write("complex(0.0, 999.0) = %O\n", complex(0.0, 999.0)); write("complex(0.0, 0.0) = %O\n", complex(0.0, 0.0)); write("%O->real = %O\n", c1, c1->real); write("%O->imag = %O\n", c1, c1->imag); write("%O + %O = %O\n", c1, c2, c1 + c2); write("%O - %O = %O\n", c1, c2, c1 - c2); write("%O * %O = %O\n", c1, c2, c1 * c2); write("%O.abs = %O\n", c1, c1->abs); complex i = complex(0.0, 1.0); foreach (({ 3.0, 1, -2, "123" }), mixed x) { write("\ncomplex(%O) = %O\n", x, complex(x)); write("\t%O + %O = %O\n", x, i, x + i); write("\t%O - %O = %O\n", x, i, x - i); write("\t%O * %O = %O\n", x, i, x * i); } return 0; }
- 実行結果
complex(999.0, 0.0) = 999.0 complex(0.0, 999.0) = 999.0i complex(0.0, 0.0) = 0.0 (3.0 + 4.0i)->real = 3.0 (3.0 + 4.0i)->imag = 4.0 (3.0 + 4.0i) + (1.5 - 1.0i) = (4.5 + 3.0i) (3.0 + 4.0i) - (1.5 - 1.0i) = (1.5 + 5.0i) (3.0 + 4.0i) * (1.5 - 1.0i) = (8.5 + 3.0i) (3.0 + 4.0i).abs = 5.0 complex(3.0) = 3.0 3.0 + 1.0i = (3.0 + 1.0i) 3.0 - 1.0i = (3.0 - 1.0i) 3.0 * 1.0i = 3.0i complex(1) = 1.0 1 + 1.0i = (1.0 + 1.0i) 1 - 1.0i = (1.0 - 1.0i) 1 * 1.0i = 1.0i complex(-2) = -2.0 -2 + 1.0i = (-2.0 + 1.0i) -2 - 1.0i = (-2.0 - 1.0i) -2 * 1.0i = -2.0i complex("123") = 123.0 "123" + 1.0i = (123.0 + 1.0i) "123" - 1.0i = (123.0 - 1.0i) "123" * 1.0i = 123.0i
このPikeのコードは、complex
(複素数)クラスを定義し、複素数の演算や機能を実装しています。以下に各部分の解説をします。
まず、complex
クラスの定義です。クラス内で使用されるプライベートな変数 _real
と _imag
が定義されています。
また、`real()
メソッドと `imag()
メソッドが定義され、それぞれ _real
と _imag
の値を返します。
次に、create()
メソッドが定義されています。このメソッドは、コンストラクタの役割を果たします。
可変長引数を受け取り、最初の引数を実部 _real
に、残りの引数(もし存在する場合)を虚部 _imag
に代入します。
加算、減算、乗算の演算子メソッドが定義されています。例えば、+
演算子に対して complex
クラスのインスタンスとの加算を行うと、実部と虚部をそれぞれ足した結果の新しい complex
インスタンスが返されます。また、+
演算子に対して mixed
型(異なる型の加算も許容する)の引数を受け取る場合は、引数を実部として足し合わせた新しい complex
インスタンスが返されます。
`abs
メソッドは、複素数の絶対値を返すためのメソッドです。絶対値は実部の二乗と虚部の二乗の和の平方根として計算されます。
_sprintf
メソッドは、複素数を文字列として表示するためのフォーマットを提供します。
最後に、main
関数では、complex
クラスのオブジェクトを作成し、それらの各メソッドを呼び出しています。
また、foreach
ループを使って、配列の各要素について complex
オブジェクトを作成し、それらの加算、減算、乗算を計算しています。
継承と包含
[編集]継承は、既存のクラスを基盤として、新しいクラスを作成することができる仕組みです。継承により、既存のクラスのすべてのフィールドとメソッドを新しいクラスに引き継ぐことができます。また、新しいクラスには、継承元のクラスには存在しないフィールドやメソッドを追加することができます。これにより、より特化したクラスを作成することができます。
Pikeでは、クラス継承はinherit
キーワードを使用して宣言されます。継承元のクラスは、inherit
キーワードの後に指定されます。
図形を例にクラスの継承を解説します。
- inherit.pike
class point(int x, int y) { point move(int dx, int dy) { x += dx; y += dy; write("point::move\n"); return this; } string _sprintf() { return sprintf("point(%d, %d)", x, y); } } class shape { point location; void create(int x, int y) { location = point(x, y); write("shape::constructor\n"); } shape move(int x, int y) { location.move(x, y); write("shape::move\n"); return this; } string _sprintf() { return sprintf("location: %s", location->_sprintf()); } } class rectangle { inherit shape; int width; int height; void create(int x, int y, int width, int height) { ::create(x, y); this.width = width; this.height = height; write("rectangle::constructor\n"); } string _sprintf() { return sprintf("rectangle { %s, width: %d, height: %d }", ::_sprintf(), width, height); } } int main() { write("Create a rectangle!\n"); rectangle rct = rectangle(12, 32, 100, 50); write("rct = %O\n", rct); rectangle rct2 = rectangle(1, 2, 10, 150); write("rct = %O\n", rct); write("rct2 = %O\n", rct2); return 0; }
まず、point
クラスが定義されています。このクラスは2次元の座標を表します。コンストラクタでx座標とy座標を受け取り、その値を保持します。move
メソッドは、座標を指定した量だけ移動させるためのメソッドです。内部でx座標とy座標を更新し、移動したことを表示します。また、_sprintf
メソッドは、オブジェクトの文字列表現を返すための特殊なメソッドです。
次に、shape
クラスが定義されています。このクラスは図形を表すための基本クラスです。shape
クラスはpoint
オブジェクトを保持しており、その位置を表します。create
メソッドは、指定された座標に新しいpoint
オブジェクトを作成し、location
に代入します。move
メソッドは、location
の座標を指定した量だけ移動させるためのメソッドです。内部でpoint
オブジェクトのmove
メソッドを呼び出しています。また、_sprintf
メソッドは、オブジェクトの文字列表現を返すための特殊なメソッドです。
最後の、rectangle
クラスが定義されています。このクラスはshape
クラスを継承しており、矩形を表します。create
メソッドでは、親クラスのcreate
メソッドを呼び出して位置を設定し、さらに幅と高さも設定します。_sprintf
メソッドでは、親クラスの_sprintf
メソッドを呼び出して親クラスの情報を表示し、それに続けて矩形固有の情報である幅と高さも表示します。
main
関数では、実際にrectangle
オブジェクトを作成し、操作結果を表示しています。最初にrct
という名前の矩形オブジェクトを作成し、その次にrct2
という別の矩形オブジェクトを作成しています。各オブジェクトの情報を表示しています。
多重継承
[編集]時には、2つまたはそれ以上のクラスから継承したい場合があります。これはPike(およびC++)で動作しますが、Javaでは動作しません。ただ、複数の継承を記述すれば良いです。
class B { ... } class C { ... } class D { inherit B; inherit E; }
アクセス修飾子
[編集]の例では、すべてのオブジェクトのすべてのメソッドとメンバ変数に誰でもアクセスできました。 例えば、次のように年齢を減らすことが非常に簡単でした。
john->age -= 50;
あれ?このjohnの年齢は30歳で、今はマイナス20歳になっている?
他のクラスからその変数に触れないように、メンバ変数 age へのアクセスを制御したいと思います。 このような用途のために、メソッドまたはメンバ変数の定義で、データ型の前に書かれるいくつかのアクセス修飾子があります。 例えば、人物の年齢はメンバ変数 age によって表されます。
int age;
これを変更して、
private int age;
とすると、同じクラス内のメソッドのみがその変数にアクセスできるようになります。
次のアクセス修飾子が存在します。
- public
- これがデフォルトで、任意のメソッドがメンバ変数にアクセスしたり、メソッドを呼び出したりできます。
- private
- メンバ変数またはメソッドが、同じクラス内のメソッドのみで利用可能であることを意味します。
- static
- このメンバ変数またはメソッドは、同じクラス内のメソッドおよびサブクラスのメソッドのみが使用できます(Pikeの static は、C++の static とまったく同じ意味ではなく、代わりに C++の protected に似ています)。
- local
- このメソッドがサブクラスのメソッドによってオーバーライドされていても、このクラスのメソッドはこのメソッドを使用し続けます。
- final
- サブクラスがこのメソッドを再定義できないようにします。
クラスにコンストラクタ(つまり、create という名前のメソッド)がある場合、これを static に宣言することが良いアイデアです。 それはオブジェクトの構築中にのみ呼び出されるべきであり、static でない場合、継承に関連するいくつかの型の不整合が生じる可能性があります。
ファイル操作
[編集]Pikeでは、ファイルI/Oに Stdio オブジェクトを使います。
ファイルの読み出し
[編集]int main() { constant fname = "/etc/hosts"; string|int buff = Stdio.read_file(fname); if (intp(buff)) { Stdio.perror(fname); return 1; } write(buff); return 0; }
このPikeのコードは、ファイル "/etc/hosts" を読み込み、その中身を標準出力に出力するプログラムです。
まず、string
型の変数 fname
に "/etc/hosts" というファイルのパスを代入しています。次に、Stdio
モジュールの read_file()
関数を使って、fname
に指定されたファイルを読み込んでいます。読み込んだ結果は、string|int
型の変数 buff
に代入されます。
string|int
型とは、string
型または int
型のいずれかの値が代入可能な型です。これは read_file()
関数が、ファイルの読み込みに成功した場合はファイルの内容を string
型で返し、失敗した場合は 0
を返すためです。エラーが発生した場合、intp()
関数を使って buff
の型が int
型であることを確認し、Stdio.perror()
関数を使ってエラーの詳細を標準エラー出力に出力した後、プログラムは終了します。
読み込みに成功した場合、write()
関数を使って、buff
の内容を標準出力に出力します。最後に、プログラムは終了します。
このコードは、システムファイル "/etc/hosts" の中身を表示するために使用されます。ただし、root権限が必要な場合があるため、注意が必要です。
ファイルの書き込み
[編集]int main() { constant fname = "sample.txt"; int cnt; int|array err = catch { cnt = Stdio.write_file(fname, "TEST TEXT"); }; if (arrayp(err)) { write(err[0]); return 2; } string|int buff = Stdio.read_file(fname); if (intp(buff)) { Stdio.perror(fname); return 1; } write(buff); return 0; }
このプログラムは、ファイルの書き込みと読み込みを行い、それが正常にできたかどうかをチェックします。
まず、main()
関数が定義されています。
次に、string fname = "sample.txt";
でファイル名を指定し、int cnt;
で書き込んだバイト数を格納する変数を宣言します。
また、エラーが発生した場合のエラーメッセージを格納する変数int|array err
を宣言しています。
この変数は、catch
によって例外が発生した場合に、理由とスタックトレースが格納されます。
catch
のブロック内では、Stdio.write_file
関数を使用して、指定されたファイルにテキストを書き込みます。
catch
ブロックの外側で、arrayp
関数を使用して、err
変数の型が配列であるかどうかをチェックします。
もし配列であれば、エラーメッセージを出力し、return 2;
でプログラムを終了します。
次に、string|int buff = Stdio.read_file(fname);
で、指定されたファイルからテキストを読み込んで、読み込んだテキストをstring
型の変数buff
に格納します。
もし読み込みに失敗した場合は、intp
関数を使用して、buff
変数の型が整数型であるかどうかをチェックします。
整数型であれば、Stdio.perror(fname);
でエラーメッセージを出力し、return 1;
でプログラムを終了します。
最後に、読み込んだテキストをwrite(buff);
で出力し、return 0;
でプログラムを正常に終了します。
ネットワークプログラミング
[編集]ソケットの概念
[編集]ソケット通信の基本
[編集]クライアント・サーバーの作成
[編集]附録
[編集]キーワード
[編集]array __attribute__ auto break case catch class constant continue default __deprecated__ do else enum extern final float for foreach __func__ function gauge global if import inherit inline int lambda local mapping mixed multiset object optional predef private program protected public return sscanf static _Static_assert string switch typedef typeof __unknown__ __unused__ variant void __weak__ while
コードギャラリー
[編集]エラトステネスの篩
[編集]エラトステネスの篩を、若干 Pike らしく書いてみました。
- エラトステネスの篩
// eratosthenes関数はエラトステネスの篩を使ってnまでの素数を見つけます。 array(int) eratosthenes(int n) { // sieve配列を初期化し、全ての数を素数と仮定します。 array sieve = allocate(n + 1, true); sieve[0] = false; // 0は素数ではない sieve[1] = false; // 1は素数ではない // エラトステネスの篩を実行します。 foreach (sieve; int i; int prime) { if (prime) { // 素数iの倍数を篩い落とします。 for (int j = i * i; j < sizeof(sieve); j += i) { sieve[j] = false; } } } // 素数を抽出します。 array(int) primes = ({}); foreach (sieve; int i; mixed prime) { if (prime) { primes += ({ i }); } } return primes; } int main() { // eratosthenes関数を使って100までの素数を求めます。 array(int) primes = eratosthenes(100); // 結果を出力します。 foreach (primes, int prime) write(prime + " "); write("\n"); }
- 実行結果
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
最大公約数と最小公倍数
[編集]最大公約数と最小公倍数を、若干 Pike らしく書いてみました。
int gcd2(int m, int n) { return n == 0 ? m : gcd2(n, m % n); } int gcd(int ... ints) { return Array.reduce(lambda(int x, int y){ return gcd2(x, y); }, ints); } int lcm2(int m, int n) { return m * n / gcd2(m, n); } int lcm(int ... ints) { return Array.reduce(lambda(int x, int y){ return lcm2(x, y); }, ints); } int main() { write("gcd2(30, 45) => %O\n", gcd2(30, 45)); write("gcd(30, 72, 12) => %O\n", gcd(30, 72, 12)); write("lcm2(30, 72) => %O\n", lcm2(30, 72)); write("lcm(30, 42, 72) => %O\n", lcm(30, 42, 72)); return 0; }
- 実行結果
gcd2(30, 45) => 15 gcd(30, 72, 12) => 6 lcm2(30, 72) => 360 lcm(30, 42, 72) => 2520
二分法
[編集]二分法を、若干 Pike らしく書いてみました。
- 二分法
// bisectionは関数fが0に等しくなる実数xを二分法で検索します。 // lowとhighは関数fの値が0より小さい範囲の下限と上限です。 // xは現在の中点です。 // fxは関数fのxの値です。 float bisection(float low, float high, function(float:float) f) { float x = (low + high) / 2.0; float fx = f(x); if (abs(fx) < 1.0e-10) return x; if (fx < 0.0) low = x; else high = x; return bisection(low, high, f); } int main(){ write("%.16f\n", bisection(0.0, 3.0, lambda(float x) { return x - 1.0; })); write("%.16f\n", bisection(0.0, 3.0, lambda(float x) { return x*x - 1.0; })); }
- 実行結果
0.9999999999417923 1.0000000000291038
- 旧課程(-2012年度)高等学校数学B/数値計算とコンピューター#2分法の例を Pike に移植しました。
リソース
[編集]Pikeに関する情報やリソースを見つけることは、他の一般的なプログラミング言語と比較して少ないかもしれませんが、いくつかの有用なリソースがあります。
以下は、Pikeに関する情報を見つけるためのいくつかのリソースです:
- Pike 公式ウェブサイト
- http://pike.lysator.liu.se/
- Pike言語の公式ウェブサイトには、言語のドキュメントやチュートリアル、リファレンスが提供されています。また、最新のリリースやコミュニティに関する情報も入手できます。
- Pikeドキュメント
- http://pike.lysator.liu.se/docs/
- Pikeの公式ウェブサイトには、言語のドキュメントが提供されており、言語の機能や構文に関する詳細な情報を得ることができます。
- Pikeソースコードのリポジトリ
- https://github.com/pikelang/Pike
- Pikeのソースコードはオープンソースで、GitHubのリポジトリで利用可能です。ソースコードを調査することで、言語の実装や最新の開発について学ぶことができます。