この記事はエムスリー Advent Calendar 2025 3日目の記事です。
こんにちは、エンジニアリンググループ マルチデバイスチームの藤原です。
皆さんはQuine(クワイン)をご存知でしょうか。 Quineとは、「自身のソースコードと完全に同じ文字列を出力するプログラム」のことです。
今回紹介するのは、引数でアスキーアートの形が変わるSwift製Quineです。一見難解なコードの裏にあるロジックを、フローチャートを使って紐解いていきます。
- Swift Quine:M3のすがた
- 変身する実行結果:M3Tのすがたへ
- データの解剖:「テンプレート」と「設計図」の秘密
- 全体フローチャート:データ参照の切り替え
- アスキーアート生成
- おわりに
- We are hiring !!
Swift Quine:M3のすがた
まずはコードを見てください。これが今回の主役です。
import Foundation;typealias S=String;var(e,n)=("\u{20}",CommandLine.arguments.count%2^1),s={(i,j)in(
0..<j).map{if(i%2==0){a.remove(at:$0&0)}else{e}}},p={b($0+20*n).enumerated},b={Data(base64Encoded:S(
l.split(separator:":")[$0]))!},r={$0+"\n"},o={S(format:S(data:b(0),encoding:.utf8)!,~$0&1,$1)},l="""
:aW1wb3J0IEZvdW5kYXRpb247dHlwZWFsaWFzIFM9U3RyaW5nO3ZhcihlLG4pPSgiXHV7MjB9IixDb21 tYW5kTGluZS5hcmd
1bWVudHMuY 291bnQlJTJeJWQpLHM9eyh pLGopaW4oCjAuLjxqK S5tYXB7aWYoaS
UlMj09MCl7YS 5yZW1vdmUoYXQ6JDAmMCl 9ZWxzZXtlfX19LHA9e2 IoJDArMjAqb
ikuZW51bWVyYXRl ZH0sYj17RGF0YShiYXN lNjRFbmNvZGVkOlMoCm wuc3Bs aXQoc2VwYX
JhdG9yOiI6IilbJ DBdKSkhfSxyPXskM CsiXG4ifSxvPXtTKGZv cm1hdDpTK GRhdGE6Yig
wKSxlbmNvZGluZz oudXRmOCkhLH4kM CYxLCQxKX0sbD0iIiIKJUAiIiIuZmls dGVyeyEoJD
A9PSJcbiJ8fFMoJ DApPT1lKX0sYT 1sLm1hcHtTKCQwKX07cHV0cyhvKG4sK DEuLjwyMSku
bWFwe3IocCgkMCk oKS5tYXB7cyg kMC4wLEludCgkMC4xCikpLypHZXQqL y5qb2luZWQvK
mluLCovKCl9LypT d GF5Ki8uam9 pb mVkLyp0byB0aGUgbWlzc2lvbiw qLygpKX0vKkNyZ
WF0ZSovLmpvaW5l ZC 8qaW5ub3Z hdG lvbiEqLygpKSkKLy89PT09P T09PT09PT09P
T09PT09PT0gV2U gYXJ lIGhpcm luZ yAhISA6IGh0dHBzOi8va m9icy5tMy5
jb20vZW5naW5lZ XIvID 09PT 09PT 09PT09PT09PT09PT09PT09PT0vLw= =:UAQQ:Cg
0WDBIMDQ==:DAs VChMQC w= =:Dwk TCRMFBggK:DwsQChMDCQcK:DwsPCx8H Cg==:Dww
NDB8GCw==:Dw0M DB4GDA= = :DwQBC QoEAgcaCA4=:DwQCCAkEAwgXDAw=:DgQ ECAcFAwg
UEQo=:DgQFCQQ FBAgdCQk =:DgQGCQ IFBQgfCAg=:DgQHCAEFBgggBwg=:DQQ IDQgIHwc
I:DQQJCwkIHgg I:DQQKCQo IEAMLBwk= :DQQLBwsIDwQKBwo=:CA4HBgcRChMM :CA4IBAg
RDA4P:ZA==:ZA ==:EAYPBgE RARYQ:EAcN MBA=:EAgLMRA=:EA kJEAUdEA==: EAoHCgwHC
AYY:EAwDDAwHC AYY:EBsFDQk GGA==:EBsFD AoGGA==:EBsFDQk GGA==:EAYD CQMGDAcIBh
g=:EAYFB QUGDAcI Bhg=:EA YPBgwHCAYY :EAYPDQUHBgo
W:EAYPGQ YKFg==:E AYPGQYKF g==:EAYPBgER BwoW:ZA==:ZA==:
""".filter{!($0=="\n"||S($0)==e)},a=l.map{S($0)};puts(o(n,(1..<21).map{r(p($0)().map{s($0.0,Int($0.1
))/*Get*/.joined/*in,*/()}/*Stay*/.joined/*to the mission,*/())}/*Create*/.joined/*innovation!*/()))
//===================== We are hiring !! : https://jobs.m3.com/engineer/ =========================//
Swiftのコード全体で「M3」という文字のアスキーアートを形成しています。
以前にも iOSDC Japan 2025専用のSwift Quineを作成したことがあるのですが、今回は「汎用Swift Quine」として、改めて設計し直しました。
変身する実行結果:M3Tのすがたへ
このQuineの特徴は、実行方法によってその出力結果が変わる点にあります。
1. 通常モード(引数なし)
ターミナルで swift m3.swift のように実行すると、上記のソースコードと全く同じ、「M3」アスキーアートが出力されます。
2. 変身モード(引数あり)
しかし、swift m3.swift change のように引数を渡すと、出力結果が変化します。
import Foundation;typealias S=String;var(e,n)=("\u{20}",CommandLine.arguments.count%2^0),s={(i,j)in(
0..<j).map{if(i%2==0){a.remove(at:$0&0)}else{e}}},p={b($0+20*n).enumerated},b={Data(base64Encoded:S(
l.split(separator:":")[$0]))!},r={$0+"\n"},o={S(format:S(data:b(0),encoding:.utf8)!,~$0&1,$1)},l="""
:aW1wb3J0IEZvdW5kYXRpb247dHlwZWFsaWFzIFM9U3RyaW5nO3ZhcihlLG4pPSgiXHV7MjB9IixDb21tYW5kTGluZS5hcmd1bWV
udHMuY291bnQlJTJeJWQpLHM9eyhpLGopaW4oCjAuLjxqKS5tYXB7aWYoaSUlMj09MCl7YS5yZW1vdmUoYXQ6JDAmMCl9ZWxzZXt
lfX19LHA9e2IoJDA rMjAqbikuZW51bW V y YXRlZH0sYj17RGF0
YShiYXNlNjRFbmNv ZGVkOlMoCmwuc 3BsaXQoc2VwYXJhd
G9yOiI6IilbJDBdK SkhfSxyPXsk MCsiXG4ifSxvPXtT
KGZvcm1hdDpTKGRh dGE6YigwK Sxlbm NvZGluZzoudXRmOC
khLH4kMCYxLCQxKX 0sbD0iI iIKJUAiIiIuZ mlsdGVye yEoJDA9PSJcbiJ8fFMoJDApP
T1lKX0sYT1sLm1hc HtT KCQwKX07cHV0 cyhvKG4s KDEuLjwyMSkubWFwe3IocCgk
MCkoKS5tYXB7cygk MC4wL EludCgkMC 4xCikpLypHZXQqLy5qb2luZW
QvKmluLCovKCl9Ly pTdGF 5Ki8uam9pb mVkLyp0byB0aGUgbWlzc2lvb
iwqLygpKX0vKkNyZ WF0ZS ovLmpvaW5 lZC8qaW5ub3ZhdGlvbiEqLyg
pKSkKLy89PT09PT0 9PT 09P T09PT09PT09P T0gV2UgY XJlIGhpcmluZyAhISA6IGh0d
HBzOi8vam9icy5tM y5jb2 0vZW5 naW5lZXIvID0 9PT09PT0 9PT09PT09PT09PT09PT09PT0
vLw==:UAQQ:Cg0WD BIMDQ==:DAsVChM QCw==:DwkTCR MFBggK:D wsQChMDCQcK:DwsPCx8HCg==
:DwwNDB8GCw==:Dw 0MDB4GDA==:DwQB CQoEA gcaCA4 =:DwQCCAkEAwgXDAw=:DgQ
ECAcFAwgUEQo=:Dg QFCQQFBAgdCQk=: DgQGCQ IFBQgfCAg=:DgQHCAEFBgg
gBwg=:DQQIDQgIHw cI:DQQJCwkIHggI :DQQKC QoIEAMLBwk=:DQQLBwsIDw
QKBwo=:CA4HBgcRC hMM:CA4IBAgRDA4 P :ZA==:Z A==:EAYPBgERARYQ:EAcNM
BA=:EAgLMRA=:EAkJEAUdEA==:EAoHCgwHCAYY:EAwDDAwHCAYY:EBsFDQkGGA==:EBsFDAoGGA==:EBsFDQkGGA==:EAYDCQMGD
AcIBhg=:EAYFBQUGDAcIBhg=:EAYPBgwHCAYY:EAYPDQUHBgoW:EAYPGQYKFg==:EAYPGQYKFg==:EAYPBgERBwoW:ZA==:ZA==:
""".filter{!($0=="\n"||S($0)==e)},a=l.map{S($0)};puts(o(n,(1..<21).map{r(p($0)().map{s($0.0,Int($0.1
))/*Get*/.joined/*in,*/()}/*Stay*/.joined/*to the mission,*/())}/*Create*/.joined/*innovation!*/()))
//===================== We are hiring !! : https://jobs.m3.com/engineer/ =========================//
アスキーアート部分の形状が「M3T」になっています。この出力されたコードもまた、コンパイル可能で実行可能なQuineとなっています。さらに、「M3T」のQuineを引数ありで実行すると「M3」の形状に戻ります。
隠されたメッセージ:「M3T」とは?
「M3」は分かるけど、変身後の「M3T」って何? と思われた方もいらっしゃるかもしれません。 これは 「エムスリーテクノロジーズ (M3 Technologies)」 を意味しています。
エンジニアリングへの遊び心を込めつつ、「エムスリーグループ全体をさらに加速させるべくチャレンジしているエムスリーテクノロジーズをよろしく!」という思いをこの形にしました。
日々どのような技術的挑戦をしているのか、その魅力について語っている記事がありますので、このQuineで興味を持ってくださった方はぜひ読んでみてください!
データの解剖:「テンプレート」と「設計図」の秘密
さて、ここから技術的な解説です。
一体どうやって、コードの整合性を保ちながら形を変えているのでしょうか? その秘密は、コードの大半を占める変数 l(小文字のエル)にあります。
この l は以下のような特性を持っています。
- 空白と改行の除去:
変数
lは、コード内の Base64 文字列部分(見た目でアスキーアートになっている部分)から、改行コードと空白文字をすべて取り除いた状態で保持されています。 - 空白数の保存則: また、変身前後で整合性を保つため、アスキーアート部分に含まれる空白の総数は、M3モードとM3Tモードで全く同じになるように設計されています。
- M3とM3Tで「不変」:
そのため、見た目のアスキーアートが「M3」であろうと「M3T」であろうと、プログラムとして読み込まれる変数
lの中身は完全に同じ文字列になります。
この l の中身をコロン : で区切ることで、以下のようにデータを使い分けています。
// lの中身の概念図 l = "テンプレートデータ:行データ1:行データ2:...:行データ40"
プログラムは l.split(separator:":") を実行し、分割されたデータをBase64デコードして以下のように使い分けています。
1. 骨組みを作る「テンプレート」
分割されたデータの最初の要素(インデックス0)は、アスキーアート部分以外の、プログラム全体の骨組み(テンプレート)です。
これをBase64デコードすると、import Foundation... から始まるコードが現れますが、「アスキーアートの文字列」と「アスキーアートがどちらのモードなのかの情報」が入る部分だけが %@ などのプレースホルダになっています。
2. 形を決める「設計図」
分割されたデータの2番目以降の要素は、アスキーアートを描くための「設計図データ」です。 ここには「空白を何個、文字を何個置くか」という情報が圧縮されて格納されています。
全体フローチャート:データ参照の切り替え
この仕組みを踏まえた上で、全体の処理フローを見てみましょう。
モードによる参照データの変化
プログラム冒頭で決定される変数 n(M3モードの場合、引数なし=0, 引数あり=1)が、配列のどこを読むかを決定します。
- テンプレートの取得: 常に
lの 0番目 をデコードして使用。 - 設計図(M3のすがた):
n=0の時は、lの 1〜20番目 をデコードして使用。 - 設計図(M3Tのすがた):
n=1の時は、lの 21〜40番目 をデコードして使用。
このように、引数をスイッチとして「読むべき設計図のページ」を切り替えることで、同じコードから全く異なる形状を出力しています。
アスキーアート生成
最後に、デコードされた「設計図」を使って、どのようにアスキーアート(文字列)を組み立てているのか、その生成ロジックを見てみましょう。
ここでのポイントは、文字を描画する際の「インク」のような存在である変数 a です。
変更可能な変数として a にアスキーアート部分の文字列全体を格納しておき、設計図の指示に従って a から文字を順番に取り出しては並べるという処理を行っています。
- 設計図「次は10文字」→
aから10文字取り出して追加 - 設計図「次は13空白」→
を13追加 - 設計図「次は22文字」→
aから22文字取り出して追加
また、この設計図データにはある重要な法則があります。 それは、1行分のByte列(設計図)の数値を合計すると、必ず「100」になるという点です。
「10(文字) + 13(空白) + 22(文字) + ...」のように数値を足していくと、M3モードでもM3Tモードでも、どの行も必ず合計が100になります。 これはアスキーアートの1行の文字数(幅)と一致しており、この制約を守ることで、出力されるコードが綺麗な長方形のブロック形状を維持できるようになっています。
おわりに
Quineの解読はこれで終了です。
今回解説したレイアウト手法ですが、過去に作成されたM3 Quineとは全く異なるアプローチを採用しています。 もし興味があれば、他のQuine解説記事もぜひ覗いてみてください。アプローチの違いを楽しんでいただけるはずです。
直近の過去記事はこちらです。
Quineには「正解」がありません。言語仕様の隙間を縫って、自分だけの表現方法を見つけるプロセスこそが醍醐味です。 皆さんもぜひ、オリジナルのQuine作りに挑戦してみてはいかがでしょうか?
We are hiring !!
最後に、このQuineのコード末尾に隠されたメッセージを紹介します。
))/*Get*/.joined/*in,*/()}/*Stay*/.joined/*to the mission,*/())}/*Create*/.joined/*innovation!*/())) //===================== We are hiring !! : https://jobs.m3.com/engineer/ =========================//
配列を連結するSwiftのメソッド .joined の間にコメントを刻み、"Get joined in, Stay joined to the mission, Create joined innovation!" という一文を作りました。日本語に訳すなら、「仲間になり、ミッションを共有し、共にイノベーションを創り出そう!」 といったところでしょうか。
エムスリーではイノベーションを .joined してくれるエンジニアを絶賛募集中です。
このQuineのような技術の無駄遣い(?)を愛しつつも、本番にデプロイするコードは真剣に書く。そんなギークでスマートな方のご応募をお待ちしています。