自分自身のソースを出力するプログラム

hq9+というジョープログラミング言語を通じて「自分自身のソースを出力するプログラム」を書く事が言語によっては難しい課題である事を知り、erlangで実装してみる事に。

問題の構造

selfn.erl:
-module(selfn).
-export([start/0]).
start() ->
    {ok, A} = file:read_file("selfn.erl"),
    io:format("~s", [A]).

これは、インチキしているので却下。自分自身に関するデータは自分自身で持っていないとつまらない。なるだけ正攻法で行こう。
そうすると、

start() -> io:format("start() -> io:format(\"start() -> ...

うーむ。無限の入れ子になってしまう。ようするにこれをなんとかするということか。

無限入れ子を解きほぐしてみる

再帰構造なので、再帰チックに解けそうだ。要は、一段階だけの再帰となるように問題を工夫すればいいはず。そこで、自分自身を出力するプログラムを直接記述するのではなく、自分自身を出力するプログラムを出力するプログラムを作るつもりで考えてみる。そういう関数をfunc/1とすると、

-module(self).
func(A) -> %% Aには書式コード~sが一つだけあるものとする。
    io:format(A, [A]).

基本的なアイディアはこれ。

多重エスケープ問題

他には引用符をどうするかがあるので、リテラル中に引用符を記述しなくてすむように工夫する。具体的には、文字列中の二重引用符はシングル引用符で記述しておき、decode/1で変換することで多重エスケープ問題を回避する。

-define(DQ, 16#22).
-define(SQ,  16#27).
decode(A) -> lists:map(fun(?SQ) -> ?DQ; (X) -> X end).
func(A) ->
    io:format(decode(A), [A]).

全体構成

Aの中身は最初は考えず作成してしまって、あとで、自分自身を文字列化してAに設定するようにすればいい。

-module(self).
-export([start/0]).
-define(DQ, 16#22).
-define(SQ,  16#27).
decode(A) -> lists:map(fun(?SQ) -> ?DQ; (X) -> X end).
func(A) ->
    io:format(decode(A), [A]).

start() ->
    A = "~s",
    func(A).

結論

erlangの文字列リテラルは改行文字を含んでいても問題ない事に注意して、これらをまとめると

-module(self).
-export([start/0]).
-compile(export_all).
-define(DQ, 16#22).
-define(SQ, 16#27).
decode(A) ->
    lists:map(fun(?SQ) -> ?DQ; (X) -> X end).

func(A) ->
    A2 = decode(A),
    io:format(A2, [A]).

start() ->
    A1="-module(self).                                                             
-export([start/0]).                                                                
-compile(export_all).                                                              
-define(DQ, 16#22).                                                                   
-define(SQ, 16#27).                                                                   
decode(A) ->                                                                       
    lists:map(fun(?SQ) -> ?DQ; (X) -> X end).                                      

func(A) ->
    A2 = decode(A),                                                                
    io:format(A2, [A]).                                                            
                                                                                   
start() ->                                                                         
    A1='~s',                                                                       
    func(A1).",
    func(A1).

思ったより簡単に作れた。全く実用的ではないが、なんとなく面白い。