# Delphi で前前前世 --- tags: Delphi programming Pascal embarcadero objectpascal created_at: 2020-07-07 updated_at: 2020-08-18 --- # はじめに 面白そうなお題を目にしました。 ![image.png](./images/5bf939d3-2079-c6d1-5b40-bdc5ee82df12.png) - [RADWIMPS (Github)](https://github.com/approvers/RADWIMPS) これを **Delphi** でやってみます。使用する **Delphi** は **10.4 Sydney** です。 # コード ルールを確認します。 **ルール** - `then().then().then().世()`のようなメソッドチェーンで "前前前世" と末尾に改行付きで出力すればOKです。 - `Promise`や`Future`等の言語機能があればぜひ活用してください。 ## ■ 基本的な考え方 **Delphi 2006** から導入された**高度なレコード型 (Advanced Record)** を使えば**メソッドチェーン (Method Chaining)** を実現できます。クラスでもいいのですが、レコード型の方がお手軽です。 ```pascal type TRADWIMPS = record function &Then: TRADWIMPS; function 世: TRADWIMPS; end; function TRADWIMPS.&Then: TRADWIMPS; begin Write('前'); Result := Self; end; function TRADWIMPS.世: TRADWIMPS; begin Writeln('世'); Result := Self; end; ``` `Then()` に `&` が付いているのは **Pascal** では **then** が予約語だからです。**Delphi** では予約語とカブる識別子の先頭に `&` を付けることで名前重複の問題を回避できます [^1]。この機能は他言語のライブラリなどを移植する時に重宝します。 また、**Delphi 2009** 以降だと Unicode に対応しているので、`世()` という名前のメソッドが通ります。 **See also:** - [高度なレコード型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E3.83.AC.E3.82.B3.E3.83.BC.E3.83.89.E5.9E.8B.EF.BC.88.E9.AB.98.E5.BA.A6.EF.BC.89) - [識別子 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AA%E6%A7%8B%E6%96%87%E8%A6%81%E7%B4%A0%EF%BC%88Delphi%EF%BC%89#.E8.AD.98.E5.88.A5.E5.AD.90) ## ■ コード例 (Delphi) コンソールアプリケーションとして作ってみるとこんな感じになります。 ```pascal:RADWIMPS.dpr program RADWIMPS; {$APPTYPE CONSOLE} type TRADWIMPS = record function &Then: TRADWIMPS; function 世: TRADWIMPS; end; function TRADWIMPS.&Then: TRADWIMPS; begin Write('前'); Result := Self; end; function TRADWIMPS.世: TRADWIMPS; begin Writeln('世'); Result := Self; end; begin var RADWIMPS: TRADWIMPS; RADWIMPS.&Then.&Then.&Then.世; end. { MAIN } ``` 実行してみると...エラーになりますよね? ![image.png](./images/afc6c708-c9b2-1944-6bef-3222a7fc73b1.png) **program** で プログラム名が `RADWIMPS` になっているのに、メインブロック中で識別子として **RADWIMPS** (TRADWIMPS 型) を使っているからです。これを回避するには、 ```pascal:RADWIMPS.dpr {$APPTYPE CONSOLE} type TRADWIMPS = record function &Then: TRADWIMPS; function 世: TRADWIMPS; end; function TRADWIMPS.&Then: TRADWIMPS; begin Write('前'); Result := Self; end; function TRADWIMPS.世: TRADWIMPS; begin Writeln('世'); Result := Self; end; begin var RADWIMPS: TRADWIMPS; RADWIMPS.&Then.&Then.&Then.世; end. { MAIN } ``` 先頭の **PROGRAM** ヘッダを削ります。プログラムヘッダなんて飾りです! ![image.png](./images/5b2e7c39-ecb5-ca5c-045c-1bbd7bc0e0b6.png) 今度は大丈夫でした。 ### ・補足 **標準 Pascal** ではプログラム名は識別子ではないため、 ```pascal program Sin(Output); begin Writeln(Sin(0)); end. ``` このようなコードも通ります (Sin は Pascal の標準関数です)。 ![image.png](./images/3a1911c6-6fc7-f284-cae4-799103641f0e.png) Delphi ではプログラムヘッダのパラメータは無視されますが、プログラム名は有効な識別子として扱われます。但し、先述のようにプログラムヘッダそのものを省略する事が可能です。 **See also:** - [program ヘッダー (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%A8%E3%83%A6%E3%83%8B%E3%83%83%E3%83%88%EF%BC%88Delphi%EF%BC%89#program_.E3.83.98.E3.83.83.E3.83.80.E3.83.BC) - [Delphi で最少のソースコードを書いてみる (Qiita)](./c81a8bacd7e299c45340.md) ## ■ コード例 (Free Pascal) > クラスでもいいのですが、レコード型の方がお手軽です。 レコード型の代わりに `オブジェクト型` を使う事もできます。今回のコードだと **record** を **object** に置換するだけです。 **Delphi** における `オブジェクト型` の使用は非推奨となっていますが [^2]、**Free Pascal (FPC)** では別に使用制限されていなかったと思います。 ### ・オブジェクト型を使う場合 **FPC** で`オブジェクト型` を使ったコードは次のようになります。 ```pascal type TRADWIMPS = object function &Then: TRADWIMPS; function Se: TRADWIMPS; end; function TRADWIMPS.&Then: TRADWIMPS; begin Write('前'); &Then := Self; end; function TRADWIMPS.Se: TRADWIMPS; begin Writeln('世'); Se := Self; end; var RADWIMPS: TRADWIMPS; begin RADWIMPS.&Then.&Then.&Then.Se; end. { MAIN } ``` - レコード型の代わりにオブジェクト型を使っている。 - `世()` は使えないので `Se()` に変更。 - Result 変数が使えないのでルーチン名に値を代入して結果を返している。 - インライン変数宣言ができないので **var** ブロックで変数宣言。 ### ・高度なレコード型を使う場合 **FPC** での高度なレコード型はオプション扱いなので、高度なレコード型を使いたい場合にはコンパイラ指令 `$modeswitch` に `advancedrecords` を指定します。 どうせコンパイラ指令を追加しなくてはならないのなら、`{$mode objfpc}` (と `{$H+}`) を追加すると **Object Pascal** に対応するので、Result 変数が使えるようになって便利です。 ```pascal {$mode objfpc}{$H+}{$modeswitch advancedrecords} type TRADWIMPS = record function &Then: TRADWIMPS; function Se: TRADWIMPS; end; function TRADWIMPS.&Then: TRADWIMPS; begin Write('前'); Result := Self; end; function TRADWIMPS.Se: TRADWIMPS; begin Writeln('世'); Result := Self; end; var RADWIMPS: TRADWIMPS; begin RADWIMPS.&Then.&Then.&Then.Se; end. { MAIN } ``` `{$mode objfpc}{$H+}{$modeswitch advancedrecords}` の部分を `{$mode delphi}` に変更し、 **Delphi** 互換モードで動作させる事も可能です。 ### ・スクリプトとして実行 **FPC** をインストールすると、スクリプト実行環境である **InstantFPC** もインストールされているため、コードをスクリプトとして実行させる事が可能です。 ![image.png](./images/fcc61b45-d950-f1cb-fa51-82d7eeeb02ef.png) コードの先頭に `#!/usr/bin/env instantfpc` を追加し、実行権限を与えるだけなのでお手軽です。 ### ・補足 ![image.png](./images/8df2cec4-e11b-2a0c-ee2b-e7d582ee4a60.png) **Free Pascal** のコードは [ideone.com](https://ideone.com/) で試す事ができます。**Pascal** は 2 種類あるので、試す際には `FPC` の方を選んでください。 **See also:** - [<6> Dephi のオブジェクト指向拡張 (Pascal へのオブジェクト指向拡張の歴史と Delphi) (Qiita)](./28fc0af9fc9e34a8d5c5.md) - [Lazarus (lazarus-ide.org)](https://www.lazarus-ide.org/) - [InstantFPC (Free Pascal Wiki)](https://wiki.lazarus.freepascal.org/InstantFPC) - [ideone.com](https://ideone.com/) - [Mode Delphi (Lazarus Wiki)](https://wiki.freepascal.org/Mode_Delphi) ## ■ コード例 (MPW Pascal) **Object Pascal** の始祖である **MPW Pascal (Apple Object Pascal)** でもメソッドチェーンは可能です。 ### ・一般的なコード 一般的なコードはこうなります。 ```pascal:RADWIMPS.p program RADWIMPS; uses ObjIntf; type TRADWIMPS = object(TObject) function _Then: TRADWIMPS; procedure Se; end; function TRADWIMPS._Then: TRADWIMPS; begin Write('Zen'); _Then := SELF; end; procedure TRADWIMPS.Se; begin WriteLn('Se'); end; var RADWIMPS: TRADWIMPS; begin New(RADWIMPS); RADWIMPS._Then._Then._Then.Se; RADWIMPS.Free; end. { MAIN } ``` - **PROGRAM** ヘッダがないと怒られるので追加。但し、プログラム名は識別子とみなされません。 - クラスを使います。 - TObject を使うので **uses** に `ObjIntf` を追加。 - 日本語が使えません (使い方がわかりません)。 - Result 変数が使えないので、メソッド名に結果を返しています。 - **Then** は予約語ですが、`&` による重複回避ができないので `_Then()` にしてお茶を濁しています。 - 関数の結果を読み捨てられないので、メソッド `Se()` は手続き (**procedure**) で実装しています。 ![image.png](./images/47a5078c-b8cb-1c11-fb08-1b12ffae0113.png) ### ・TObject から派生しないコード クラスを TObject から派生しなければちょっと短く書けます。 ```pascal:RADWIMPS.p program RADWIMPS; type TRADWIMPS = object function _Then: TRADWIMPS; procedure Se; end; function TRADWIMPS._Then: TRADWIMPS; begin Write('Zen'); _Then := SELF; end; procedure TRADWIMPS.Se; begin WriteLn('Se'); end; var RADWIMPS: TRADWIMPS; begin New(RADWIMPS); RADWIMPS._Then._Then._Then.Se; Dispose(RADWIMPS); end. { MAIN } ``` 短く書けますが、`Free()` メソッドは使えなくなるので、破棄処理は `Dispose()` で行う必要があります。 ### ・補足 **FPC** で試す場合には **PROGRAM** ヘッダと **uses** 句を削除した上で、`{$MODE MacPas}` コンパイラ指令を先頭に付けてください。 **See also:** - [<3> Macintosh 用 Pascal のオブジェクト指向拡張 (Pascal へのオブジェクト指向拡張の歴史と Delphi) (Qiita)](./cd245180363e1911afa7.md) - [Object Pascal (Wikipedia)](https://ja.wikipedia.org/wiki/Object_Pascal) - [ラリー・テスラー (Wikipedia)](https://ja.wikipedia.org/wiki/%E3%83%A9%E3%83%AA%E3%83%BC%E3%83%BB%E3%83%86%E3%82%B9%E3%83%A9%E3%83%BC) - [Mode MacPas (Lazarus Wiki)](https://wiki.freepascal.org/Mode_MacPas) # おわりに Delphi でのメソッドチェーンといえば、大作があるのです。Jim McKeeth 氏作の**『Code Monkey』**です。 [![image.png](./images/0623e2f3-07b9-9321-fc0b-6a91eaa7d124.png)](https://www.youtube.com/watch?v=-nGvMbQKS7U) **まずはこれを観て!** - [Code Monkey as Code in Delphi (Youtube)](https://www.youtube.com/watch?v=-nGvMbQKS7U) ソースコードは Jim 氏のサイトからダウンロードできます。 - [Code Monkey Code (The Podcast at Delphi.org)](http://delphi.org/CodeMonkey/) メソッドチェーンできる高度なレコード型を使って、コード補完でデタラメに入力しているのではなく、ちゃんとしたプログラムになっており、このコードは実際に実行可能です。 ![image.png](./images/8251db1e-35cd-8d00-373f-27bd45fc3cfb.png) 実行すると歌詞が流れます。 [^1]: `&` による識別子の重複回避は **Delphi 2005** から使えます。 [^2]: **Delphi** では比較的新しいバージョンでないとオブジェクト型によるメソッドチェーンはできません。具体的には **Delphi XE5** 以降でないと正しく動作しません。非推奨と言いながら不具合修正されているのは面白いですね。なお、クラスによるメソッドチェーンは **Delphi 1** から可能です。