エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

Goでgo fmtしたくないコードを書いた(Go版Quine)

この記事はエムスリー Advent Calendar 2025 11日目の記事です。

AI・機械学習チームの中村伊吹(@inakam00)です。

早速ですが、こちらをご覧ください。

package main;import(b"encoding/base64";f"fmt";s"strings");func main() {r:=s.ReplaceAll(s.ReplaceAll(
p," ",""),"\n","");u:=s.SplitN(r,"::M3::",2);d:=b.StdEncoding.DecodeString;t,_:=d(u[0]);m,_:=d(u[1])
;q:=f.Sprintf(string(t),r);i:=0;for n:=0;n<2600;n++{if n>0&&n%100==0{f.Println()}; x:=m[n/8];b:=(x>>
(7-uint(n%8)))&1;if b==1{f.Print(" ")}else{f.Printf("%c",q[i]);i++}};f.Println();};const p=`cGFja2Fn
ZSBtYWluO2             ltcG9ydChiImVuY29kaW5n            L2Jhc2U2NCI7ZiJmbX            QiO3Mic3RyaW5
ncyIpO2Z1bmM           gbWFpbigpIHtyOj1zLlJl          cGxhY2VBbGwocy5SZXB                sYWNlQWxsKH
AsIiAiLCIiKSwiX         G4iLCIiKTt1Oj1zLlNw         bGl0TihyLCI6Ok0zOjo     iLDIpO        2Q6PWIuU3R
kRW5jb2RpbmcuRG           Vjb2RlU3RyaW5nO3          QsXzo9ZCh1WzBdKTttL   F86PWQodV       sxXSk7cTo9
Zi5TcHJpbnRmKHN           0cmluZyh0KSxyKT           tpOj0wO2ZvciBuOj0wO248MjYwMDtuK       yt7aWYgbj4
wJiZuJSUxMDA9PT            B7Zi5QcmludGx            uKCl9OyB4Oj1tW24vOF07Yjo9KHg+Pi      g3LXVpbnQob
iUlOCkpKSYxO2lm             IGI9PTF7Zi5Q            cmludCgiICIpfWVsc2V7Zi5QcmludG      YoIiUlYyIscV
tpXSk7aSsrfX07Z    i         5QcmludGxu    KC       k7fTtjb25zdCBwPWAlc2AgICAg        ICAgICAgICAgIC
AgICAgICAgICAgI    CA        gICAgICAg    ICA        gICAgICAgICAgICAgICAgIC            AgICAgICAgIC
AgICAgICAgICAg    ICAg        ICAgICA     gIC        AgICAgICAgICAgICAgIC                 AgICAgICAg
ICAgICAgICAgIC    AgICA         gICA     gICA        gICAgICAgICAgICAgICAgICAgICAg         ICAgICAgI
CAgICAgICAgICA    gICAgI         CA     gICAg        ICAgICAgICAgICAgICAgICAgICAgICA        gICAgICA
gICAgICAgICAgI    CAgICAg        I     CAgICA        gICAgICAgICAgICAgICA=::M3::AAAAA       AAAAAAAA
AAAAAAAAAAAAA    AAAAAAAA             AAAAAAAA        AAAAAAAAAAAAAAAAAAAAAAAAAAP/4AA       Af/gAAf/
gAAAP/gAAD/wA    AH//gAAAH           /AAAf8AAB        8D/AAAAf/AAD/wAAHAH8AAAB/8AAf/        AAAAAfwA
AAH/4AD/8AAAA    B+AAAAf/wA         P/wAAAAPwA        AAB7/gB5/AAAAP8A   AAAHn+APH+A       AAD/8AAAA
8P8B8f4AAB//8    AAADwf8Ph/g       AAAA/4AAAPA        /58H+AAAAA/wAAA    8B/vgf4AAA       AB/AAAHgH/
8A/wAAAA              H8AAAeA      P/gD/AA                 AAA/wAAB4A                   f8AP8AAOAD+A
AAHgA/gA              /wAB4Afw    AAP/8B+A                 //+AH//8AAA/              /wDwD//4AH/+AAA
AAAAAAAAAAAAAAAAAAAAAABIgSAAAAAAAAAAAAIAAAAIAAAAAA==M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3
========================================= We are hiring !! =========================================
================================== https://jobs.m3.com/engineer/ ==================================`

これは実際に動作するGo言語のコードで、これを手元のローカル環境やGo Playgroundで実行すると、全く同じコードが出力されます。

go.dev

そう、これはQuine(クワイン)と呼ばれる「自分自身のソースコードを出力するプログラム」です。しかも、ソースコード自体がm3のロゴの形をしています。

Quineとは

Quineとは、外部入力なしに自分自身のソースコードを出力するプログラムのことです。ファイルを読み込んで出力するのはズルなので、プログラム内部に自身の完全な記述を持っている必要があります。*1

エムスリーでは過去に様々な言語でQuineを作成してきました。カンファレンスではクリアファイルやTシャツにそれぞれが印刷されているので、目にされたことがある方もいるでしょう。

そして今回、Go言語で挑戦しました。

Go言語でQuineを作るときの罠

AI・機械学習チームでは高速にプロダクトをリリースしつつ柔軟かつ適材適所に技術選定できる文化があり、その中でもPythonとGoは採用される率が高めです。(僕がAIチームで知っている技術スタックまとめ - エムスリーテックブログ)

Python、Swift、Kotlin、Dartと色々作られていて、いつも使っているGoでもできるでしょう...と軽い気持ちで始めたのですが、せっかくなので大変だった部分について解説していきます。

evalがない

PythonやJavaScriptにはeval関数があり、コードを文字列として定義しておき実行中に再解釈させることができます。

OCamlでQuineを作った - エムスリーテックブログではOCamlでevalに相当するものを実装しているように、アスキーアートQuineにおけるevalは非常に助かる関数です。evalがあると空白を削除してコードを実行するというテクニックが利用できるため、空白の挿入箇所が比較的自由になり、Quineとしての難度が下がります。しかし、Goでは利用できません。

数式を評価するevalは存在しますが、ここで求めているような動的にコードを実行する関数は存在しません)

m3の形をしたQuineでは上4行と下3行に空白が存在しないため、Goのようなevalがない言語では基本的にはこの行に実行コードを押し込む形をとります。*2 コードを記述できる部分が少なくなることから、evalがないアスキーアートQuineは難易度が跳ね上がります。

Python版と同様にプログラム自身をBase64エンコードして埋め込む戦略を今回はとるため、空白のある中間行はほとんどBase64文字列の定義箇所となります。

区切りをうまく調整する必要がある

evalがないことに関係しますが、ReplaceAll(...)を利用する場合にはReplaceAllという文字列の途中で改行を入れられません。また関数の場合には(の後で改行するなど、改行位置を適切にコントロールしなければなりません。

Goではif-else文の場合に}elseが同じ行に存在する必要があるため、この位置調整も大変でした。

if b == 1 {
    f.Print(" ")
} // <--- コンパイラによってここに自動でセミコロンが入り、このコードはコンパイルエラーになる
else {
    f.Printf("%c", q[i])
}

Quineを作成する

ここからは実際の制作過程です。

Base64エンコードして埋め込む戦略を実行するプログラムを作成する

先に出ている記事の多くの戦略と同様に、プログラム自身とマスク情報をBase64エンコードして埋め込む戦略を取ります。*3

完成したQuineの基本形をフォーマットしてコメントを追加すると次のようになっています。後半に定義されるconst pはプログラム自身+マスク情報+冗長な文字列のBase64文字列のため、処理部分は前半だけで完結しています。

package main

import (
    b "encoding/base64"
    f "fmt"
    s "strings"
)

func main() {
    r := s.ReplaceAll(s.ReplaceAll(
        p, " ", ""), "\n", "") // 文字列の空白を削除
    u := s.SplitN(r, "::M3::", 2) // Base64文字列をプログラムとマスク情報に分割
    // それぞれのBase64文字列をデコード
    d := b.StdEncoding.DecodeString
    t, _ := d(u[0])
    m, _ := d(u[1])
    q := f.Sprintf(string(t), r)
    i := 0
    for n := 0; n < 2600; n++ {
        if n > 0 && n%100 == 0 {
            f.Println() // 100文字ごとに改行する
        }
        // マスク情報を取り出す
        // 8bitずつ入っているフラグを取り出す
        x := m[n/8]
        b := (x >> (7 - uint(n%8))) & 1
        if b == 1 {
            f.Print(" ") // マスクが1だったら空白
        } else {
            f.Printf("%c", q[i]) // マスクが0の時は文字を出力
            i++
        }
    }
    f.Println()
}

const p=`%s`  // プログラム自身+マスク情報のBase64文字列がここに入る

Base64エンコードするためのジェネレータを作る

100文字ごとに改行してもGoのコードとして成り立つことを確認し、基本的なQuineコードが完成したら、コードやマスク情報をBase64にエンコードします。

次のようなm3ロゴをマスク情報として準備します。1となっている部分が空白になる想定です。

0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000111111111111100000000000000000000001111111111110000000000000000001111111111110000000000000
0000000000001111111111100000000000000000000011111111110000000000000000000111111111111111100000000000
0000000000000001111111110000000000000000000111111111000000000000000000011111000000111111110000000000
0000000000000001111111111100000000000000001111111111000000000000000000011100000000011111110000000000
0000000000000001111111111100000000000000011111111111000000000000000000000000000000011111110000000000
0000000000000001111111111110000000000000111111111111000000000000000000000000000000011111100000000000
0000000000000001111111111111000000000000111111111111000000000000000000000000000000111111000000000000
0000000000000001111011111111100000000001111001111111000000000000000000000000001111111100000000000000
0000000000000001111001111111100000000011110001111111100000000000000000000000111111111111000000000000
0000000000000011110000111111110000000111110001111111100000000000000000000111111111111111110000000000
0000000000000011110000011111111100001111100001111111100000000000000000000000000000111111111000000000
0000000000000011110000001111111110011111000001111111100000000000000000000000000000001111111100000000
0000000000000011110000000111111110111110000001111111100000000000000000000000000000000111111100000000
0000000000000111100000000111111111111100000000111111110000000000000000000000000000000111111100000000
0000000000000111100000000011111111111000000000111111110000000000000000000000000000001111111100000000
0000000000000111100000000001111111110000000000111111110000000000000000111000000000001111111000000000
0000000000000111100000000000111111100000000000111111110000000000000001111000000000011111110000000000
0000000011111111111111000000011111100000001111111111111111100000000001111111111111111111000000000000
0000000011111111111111000000001111000000001111111111111111100000000000011111111111111000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

ここのポイントはGoのコード部分にもfunc main()ReplaceAll(p," ","")など、動作させる上で省略できない空白があり、必要な空白部分も1にしなくてはいけません。 幸いなことに処理を実行するコード部分はBase64文字列の変数定義より前のため、エンコードされたBase64文字列の長さなどで処理部分の空白位置が変わることはありません。したがってマスクが1になる箇所は事前に計算可能で、これをマスクに反映させます。

Pythonで記述したジェネレータは次のようになります。*4*5

#!/usr/bin/env python3
from base64 import b64encode
from pathlib import Path

MASK_FILE = Path("m3_mask.txt")
OUT_FILE = Path("main.go")
DELIM = "::M3::"
WIDTH = 100

def load_mask():
    lines = MASK_FILE.read_text().splitlines()
    bits = []
    for line in lines:
        bits.extend(0 if c == "0" else 1 for c in line.strip())
    return bits

def pack_bits(bits):
    packed = []
    for i in range(0, len(bits), 8):
        chunk = bits[i : i + 8]
        while len(chunk) < 8:
            chunk.append(0)
        byte = 0
        for b in chunk:
            byte = (byte << 1) | b
        packed.append(byte)
    return b64encode(bytes(packed)).decode()

def apply_required_spaces(bits, full, rows=4):
    """上部4行のGo構文で必須な空白をマスクに反映"""
    limit = min(len(bits), WIDTH * rows)
    bits = bits[:]
    for pos in range(limit):
        bits[pos] = 0
    return bits

def build_template(mask_b64: str, pad: int):
    base = (
        'package main;import(b"encoding/base64";f"fmt";s"strings");'
        "func main() {r:=s.ReplaceAll(s.ReplaceAll(p,\" \",\"\"),\"\\n\",\"\");"
        'u:=s.SplitN(r,"::M3::",2);d:=b.StdEncoding.DecodeString;'
        "t,_:=d(u[0]);m,_:=d(u[1]);q:=f.Sprintf(string(t),r);i:=0;"
        "for n:=0;n<2600;n++{if n>0&&n%%100==0{f.Println()}; x:=m[n/8];"
        "b:=(x>>(7-uint(n%%8)))&1;if b==1{f.Print(\" \")}"
        "else{f.Printf(\"%%c\",q[i]);i++}};f.Println();};"
        "const p=`%s`"
    )
    tmpl = base + (" " * pad)
    code_b64 = b64encode(tmpl.encode()).decode()
    payload = code_b64 + DELIM + mask_b64
    full = tmpl.replace("%s", payload, 1).replace("%%", "%")
    return tmpl, full

def find_padding(bits, mask_b64, max_pad=20000):
    zeros = len(bits) - sum(bits)
    for pad in range(max_pad):
        _, full = build_template(mask_b64, pad)
        if len(full) == zeros:
            return pad, full
    raise RuntimeError(f"pad not found up to {max_pad}, zeros={zeros}")

def shape_source(bits, full):
    out = []
    idx = 0
    for pos, bit in enumerate(bits):
        if bit == 1:
            out.append(" ")
        else:
            out.append(full[idx])
            idx += 1
        if (pos + 1) % WIDTH == 0:
            out.append("\n")
    if idx != len(full):
        raise RuntimeError("mask/code length mismatch")
    return "".join(out)

def main():
    bits = load_mask()
    # Pass1: Goのコード実行部分における空白をマスクに適用する
    mask_b64 = pack_bits(bits)
    _, full0 = build_template(mask_b64, 0)
    bits = apply_required_spaces(bits, full0, rows=4)

    # Pass2: 更新されたマスクで文字数を合わせるために挿入する空白数を計算
    mask_b64 = pack_bits(bits)
    pad, full = find_padding(bits, mask_b64)

    # Pass3: 再度微調整する(不要な場合が多い)
    bits = apply_required_spaces(bits, full, rows=4)
    mask_b64 = pack_bits(bits)
    pad, full = find_padding(bits, mask_b64)

    shaped = shape_source(bits, full)
    OUT_FILE.write_text(shaped)
    print(f"pad={pad} chars, code_len={len(full)}, zeros={len(bits)-sum(bits)}")
    print(f"wrote {OUT_FILE} ({len(shaped)} chars including newlines)")

if __name__ == "__main__":
    main()

base64エンコードされた文字列に任意の文字列を追加する

このジェネレータを実行すると次のような出力になります。形はほぼ見えており、これでもクワインになっていますが、このままだとマスクに対して270文字ほど不足しています。

package main;import(b"encoding/base64";f"fmt";s"strings");func main() {r:=s.ReplaceAll(s.ReplaceAll(
p," ",""),"\n","");u:=s.SplitN(r,"::M3::",2);d:=b.StdEncoding.DecodeString;t,_:=d(u[0]);m,_:=d(u[1])
;q:=f.Sprintf(string(t),r);i:=0;for n:=0;n<2600;n++{if n>0&&n%100==0{f.Println()}; x:=m[n/8];b:=(x>>
(7-uint(n%8)))&1;if b==1{f.Print(" ")}else{f.Printf("%c",q[i]);i++}};f.Println();};const p=`cGFja2Fn
ZSBtYWluO2             ltcG9ydChiImVuY29kaW5n            L2Jhc2U2NCI7ZiJmbX            QiO3Mic3RyaW5
ncyIpO2Z1bmM           gbWFpbigpIHtyOj1zLlJl          cGxhY2VBbGwocy5SZXB                sYWNlQWxsKH
AsIiAiLCIiKSwiX         G4iLCIiKTt1Oj1zLlNw         bGl0TihyLCI6Ok0zOjo     iLDIpO        2Q6PWIuU3R
kRW5jb2RpbmcuRG           Vjb2RlU3RyaW5nO3          QsXzo9ZCh1WzBdKTttL   F86PWQodV       sxXSk7cTo9
Zi5TcHJpbnRmKHN           0cmluZyh0KSxyKT           tpOj0wO2ZvciBuOj0wO248MjYwMDtuK       yt7aWYgbj4
wJiZuJSUxMDA9PT            B7Zi5QcmludGx            uKCl9OyB4Oj1tW24vOF07Yjo9KHg+Pi      g3LXVpbnQob
iUlOCkpKSYxO2lm             IGI9PTF7Zi5Q            cmludCgiICIpfWVsc2V7Zi5QcmludG      YoIiUlYyIscV
tpXSk7aSsrfX07Z    i         5QcmludGxu    KC       k7fTtjb25zdCBwPWAlc2AgICAg        ICAgICAgICAgIC
AgICAgICAgICAgI    CA        gICAgICAg    ICA        gICAgICAgICAgICAgICAgIC            AgICAgICAgIC
AgICAgICAgICAg    ICAg        ICAgICA     gIC        AgICAgICAgICAgICAgIC                 AgICAgICAg
ICAgICAgICAgIC    AgICA         gICA     gICA        gICAgICAgICAgICAgICAgICAgICAg         ICAgICAgI
CAgICAgICAgICA    gICAgI         CA     gICAg        ICAgICAgICAgICAgICAgICAgICAgICA        gICAgICA
gICAgICAgICAgI    CAgICAg        I     CAgICA        gICAgICAgICAgICAgICA=::M3::AAAAA       AAAAAAAA
AAAAAAAAAAAAA    AAAAAAAA             AAAAAAAA        AAAAAAAAAAAAAAAAAAAAAAAAAAP/4AA       Af/gAAf/
gAAAP/gAAD/wA    AH//gAAAH           /AAAf8AAB        8D/AAAAf/AAD/wAAHAH8AAAB/8AAf/        AAAAAfwA
AAH/4AD/8AAAA    B+AAAAf/wA         P/wAAAAPwA        AAB7/gB5/AAAAP8A   AAAHn+APH+A       AAD/8AAAA
8P8B8f4AAB//8    AAADwf8Ph/g       AAAA/4AAAPA        /58H+AAAAA/wAAA    8B/vgf4AAA       AB/AAAHgH/
8A/wAAAA              H8AAAeA      P/gD/AA                 AAA/wAAB4A                   f8AP8AAOAD+A
AAHgA/gA              /wAB4Afw    AAP/8B+A                 //+AH//8AAA/              /wDwD//4AH/+AAA
AAAAAAAAAAAAAAAAAAAAAABIgSAAAAAAAAAAAAIAAAAIAAAAAA==`                                               
                                                                                                    
                                                                                                    

他の言語であれば末尾から何らかの処理をメソッドチェインで追加していき調整できますが、Go言語は思想上標準で用意されているメソッドチェインは多くありません。*6

そこで今回はBase64の末尾に文字列を追加する形で調整します。

==が出現した時点でBase64文字列は終了判定となりますが、実はその後に任意の文字列が追加されていてもデコードが可能です。 StdEncoding.DecodeStringの仕様として、任意の文字列が追加されているとデコード時にエラーが返ってきますが、同時にデコードできた部分(つまり==までの部分)の結果は関数から返ってきます。 デコード時にエラーを処理せずに握りつぶし、その結果だけを利用すれば、末尾に追加した任意の文字列は無視しつつマスク情報をデコードできます。

このテクニックを使いつつ、マスクを手動で調整し再生成した結果が冒頭のクワインとなります。*7

package main;import(b"encoding/base64";f"fmt";s"strings");func main() {r:=s.ReplaceAll(s.ReplaceAll(
p," ",""),"\n","");u:=s.SplitN(r,"::M3::",2);d:=b.StdEncoding.DecodeString;t,_:=d(u[0]);m,_:=d(u[1])
;q:=f.Sprintf(string(t),r);i:=0;for n:=0;n<2600;n++{if n>0&&n%100==0{f.Println()}; x:=m[n/8];b:=(x>>
(7-uint(n%8)))&1;if b==1{f.Print(" ")}else{f.Printf("%c",q[i]);i++}};f.Println();};const p=`cGFja2Fn
ZSBtYWluO2             ltcG9ydChiImVuY29kaW5n            L2Jhc2U2NCI7ZiJmbX            QiO3Mic3RyaW5
ncyIpO2Z1bmM           gbWFpbigpIHtyOj1zLlJl          cGxhY2VBbGwocy5SZXB                sYWNlQWxsKH
AsIiAiLCIiKSwiX         G4iLCIiKTt1Oj1zLlNw         bGl0TihyLCI6Ok0zOjo     iLDIpO        2Q6PWIuU3R
kRW5jb2RpbmcuRG           Vjb2RlU3RyaW5nO3          QsXzo9ZCh1WzBdKTttL   F86PWQodV       sxXSk7cTo9
Zi5TcHJpbnRmKHN           0cmluZyh0KSxyKT           tpOj0wO2ZvciBuOj0wO248MjYwMDtuK       yt7aWYgbj4
wJiZuJSUxMDA9PT            B7Zi5QcmludGx            uKCl9OyB4Oj1tW24vOF07Yjo9KHg+Pi      g3LXVpbnQob
iUlOCkpKSYxO2lm             IGI9PTF7Zi5Q            cmludCgiICIpfWVsc2V7Zi5QcmludG      YoIiUlYyIscV
tpXSk7aSsrfX07Z    i         5QcmludGxu    KC       k7fTtjb25zdCBwPWAlc2AgICAg        ICAgICAgICAgIC
AgICAgICAgICAgI    CA        gICAgICAg    ICA        gICAgICAgICAgICAgICAgIC            AgICAgICAgIC
AgICAgICAgICAg    ICAg        ICAgICA     gIC        AgICAgICAgICAgICAgIC                 AgICAgICAg
ICAgICAgICAgIC    AgICA         gICA     gICA        gICAgICAgICAgICAgICAgICAgICAg         ICAgICAgI
CAgICAgICAgICA    gICAgI         CA     gICAg        ICAgICAgICAgICAgICAgICAgICAgICA        gICAgICA
gICAgICAgICAgI    CAgICAg        I     CAgICA        gICAgICAgICAgICAgICA=::M3::AAAAA       AAAAAAAA
AAAAAAAAAAAAA    AAAAAAAA             AAAAAAAA        AAAAAAAAAAAAAAAAAAAAAAAAAAP/4AA       Af/gAAf/
gAAAP/gAAD/wA    AH//gAAAH           /AAAf8AAB        8D/AAAAf/AAD/wAAHAH8AAAB/8AAf/        AAAAAfwA
AAH/4AD/8AAAA    B+AAAAf/wA         P/wAAAAPwA        AAB7/gB5/AAAAP8A   AAAHn+APH+A       AAD/8AAAA
8P8B8f4AAB//8    AAADwf8Ph/g       AAAA/4AAAPA        /58H+AAAAA/wAAA    8B/vgf4AAA       AB/AAAHgH/
8A/wAAAA              H8AAAeA      P/gD/AA                 AAA/wAAB4A                   f8AP8AAOAD+A
AAHgA/gA              /wAB4Afw    AAP/8B+A                 //+AH//8AAA/              /wDwD//4AH/+AAA
AAAAAAAAAAAAAAAAAAAAAABIgSAAAAAAAAAAAAIAAAAIAAAAAA==M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3M3
========================================= We are hiring !! =========================================
================================== https://jobs.m3.com/engineer/ ==================================`

終わりに

最初はどのように作るんだろうと思いながら、AIの力も借りつつ作成を進めていき、最終的にGo言語で実現できました。 次はどの言語でクワインが作られるのでしょうか。乞うご期待。

We are hiring!!

エムスリーAI・機械学習チームでは、言語仕様を面白く扱いながらプロダクトへ活用もしていくエンジニアを募集しています。 新卒・中途それぞれの採用だけでなく、カジュアル面談やインターンも常時募集しています。

エンジニア採用ページはこちら

jobs.m3.com

カジュアル面談もお気軽にどうぞ

jobs.m3.com

インターンも常時募集しています

open.talentio.com

*1:とはいえ、ファイルを読み込むタイプのQuineもあるみたいです

*2:Swift版やKotlin版でも同じような構成になっています

*3:詳しい解説はエムスリーが難読プログラミングオタクに送るノベルティ、Python Quineクリアファイルの作り方 - エムスリーテックブログをご覧ください

*4:ジェネレータはGoじゃないんかいというツッコミは無しです

*5:やや冗長なコードなのですが、処理を保ったまま簡潔化ができなかったため実際に生成に利用したコードをそのまま提供します

*6:エラーハンドリングの仕様とメソッドチェインの相性が良くないため、Functional Option Patternが好まれます。

*7:We are hiringなどの部分に空白を入れたかったため、その部分を手動で1にしたマスクを準備し再生成しました