星にゃーんのブログ

ほとんど無害。

星にゃーんとTwitter

星にゃーん。彼はTwitterで生まれた、僕の分身のような、息子のような、父のような、そんなフィクションのキャラクター。今だとVTuberみたいなやつ。

だから、星にゃーんとして活動するときには、星にゃーんを演じるようにしている。彼は尊重するべき一つのキャラクターで、彼の評価は演者の僕の腕にかかっているわけだ。


星にゃーんはショートショートを書くのが好きだ。ジャンルはSFとジュブナイル恋愛、父と子の対話劇、風刺めいた歌詞風の詩など、さまざまだ。

最近はインターネットに投稿し始めたらしい。ここから読める。

kakuyomu.jp


星にゃーんはほら話が好きだ。例をいくつかあげてみよう。ほんとうにひどいんだから。

C言語のソースファイルの拡張子は.cと、国際ファイルフォーマット総会が定めた国際拡張子基準で決まってる」

「みかんの繊維は、栄養価を高めるために、製造段階で実の部分に縫い付けられている。そのあと皮を被せ、お店に並ぶんだ。食塩にヨウ素を添加する地域があるのと似たようなもの。」

「プログラムをコンパイルすると、インターネットを通じてアメリカのシリコンバレーにある国際コンパイラステーションに送られ、厳しい試験と訓練をクリアした優秀な人たちが一行一行手間暇込めてコンパイルしている。コンパイルエラーは担当者からの手書きのメッセージ。」


星にゃーんはプログラミングが好きだ。特に、コンパイラインタプリタ、言語VM、標準ライブラリ、パッケージマネージャ、IDEが好きだ。 こんなものを作ってる。

github.com


星にゃーんを演じるのはとても楽しい。何より気楽だし、やりがいがある。一種の自己プロデュースとしての側面もあって、他者からの評価や信用もどうやら得られているようだ。この奇人のことを多少なりとも信用するとは、人の懐の広さには頭が下がる。

僕はどんな人かって?それは、会ってみてのお楽しみ。ほんの少しの幸運があれば、話をしよう。

著:こうのゆうや

シン・星にゃーん

昔のことを思い出した。忘れていた記憶だった。

彼はいつも泣いていた。悲しくて泣いていた。寂しくて泣いていた。友だちとうまく話せなくて、不甲斐なくて泣いていた。冥王星が惑星でなくなったと聞いて、悔しくて泣いていた。7×3が分からなくて、怖くて泣いていた。月があまりにも遠いから、虚しくて泣いていた。

エヴァンゲリオンを観たのは、まだ彼が泣いていた頃のことだ。エヴァ好きの友達に勧められて、友達の家のテレビで見た。プラグスーツ、エントリープラグ、汎用人型決戦兵器。どちらかと言えばアスカ派だった。あいつが熱く語るものだから。

それからしばらく経った、2015年6月22日。記憶の限り、彼はほとんどエヴァンゲリオンのことを忘れていた。もちろん、エヴァを語る機会はたくさんあった。しかし、本質的な部分で、彼はエヴァンゲリオンに興味をなくしていた。ある友達が、彼の家に遊びにきた。インターホンに出てみると、「使徒、襲来」。

f:id:takoeight0821:20210410200649j:plain

もうそんな時代なんだ。まだまだJAは作れそうにないし、ジオフロントなんてもってのほかだな。そう笑いあった。そして、エヴァンゲリオンは過去になった。

彼は自分の人生を見失った。運命に手を出そうとして、逆に運命に手を出された。音楽が苦痛になった。違和感を感じたときには、首までどっぷり嵌っていた。初めに、物語が読めなくなった。空想の世界から弾き出された。次に、身体が動かなくなった。ベッドから起き上がれなくなった。最後に、食べることを忘れた。食事を取らなくなった。

母は「見てる方もしんどい」と彼に言った。父は「今はそのままでいい」と彼に言った。妹は何も言わなかった。友達はいつも通りに接した。皆が彼に優しくした。彼は放っておいてくれと言った。何も食べずに死ぬつもりだった。

父は彼の口にむりやり茹で卵を押し込んだ。彼は食べ、水を飲むしかなかった。少しすると、急に空腹が襲ってきた。耐え切れずにひたすら食べた。

彼は実態を伴った高い評価を受けるようになった。食べなくなる前の、過去の記憶が彼の腕を動かし、その結果が評価され始めた。生きる理由とはならなかったが、死ぬ理由もなくなった。

エヴァンゲリオンは2008年に完結する予定だった。 エヴァンゲリオンは彼にとって過去のものだったが、エヴァンゲリオンの物語は彼の人生の物語だった。

彼は常に物語を通して自分の人生と向き合ってきた。フラグタイムも、仮面ライダージオウも、ツインスター・サイクロン・ランナウェイも、彼にとっては自分の人生のミニチュアだった。彼には自らの人生を主観的に受け止める覚悟がなかった。物語に仮託することで人生を認識してきた。物語を拒絶してもなお、彼にとっての人生は誰かの書いた物語だった。

彼はシン・エヴァンゲリオンを恐れていた。碇シンジの物語を受け止める勇気がなかった。それはただの物語ではなく、彼自身の物語でもありえたから。 しかし、いつまでも逃げているわけにもいかなかった。 過去の落とし前をつけなければならなかった。

劇場で、彼は涙を流していることに気づいた。彼にとって、涙は過去のものだった。泣いていた。新しい涙だった。誰かのために流す涙だった。喜びのそばを流れる涙だった。

彼は駅に立っていた。家路につきたかったが、どの電車に乗ればいいのか分からなかった。どの線路が自分の人生なのか分からなかった。

「赤いラベルのホームだよ。すぐに迎えが来る。」

彼にとっては、その一言で十分だった。

「ありがとう。また。」

電車はすぐにやってきた。扉をくぐって、ふと思った。彼はどこに帰るんだろう。月はあまりにも遠かった。

自作プログラミング言語Malgoがかなりそれっぽくなってきた

プログラミング言語Malgo

2017ごろから、自作のプログラミング言語Malgoとそのコンパイラを作っている。 2018年の2月に、このことをブログに書いた。 あれから三年経ち、色々とできることが増えた…かというと、そうでもない。 しかし、言語設計はかなり変わった。ガワだけ見るとほとんど別物だ。

当時のブログ:https://takoeight0821.hatenablog.jp/entry/2018/02/28/122847

現在のMalgoは、Haskellに影響を受けた構文を持つ、非純粋な正格評価の言語だ。 型推論高階関数、代数的データ型などの、いわゆる「関数型」な言語機能を備えている。 例えば、フィボナッチ数を計算するプログラムはこんな風に書く:

-- Fibモジュールの宣言
module Fib = {
  -- Builtinモジュールのインポート
  import Builtin;
  import Prelude;

  -- 外部関数newlineのインポート
  -- newlineの実装はCで書かれている
  foreign import newline :: () -> ();

  -- 中置演算子<=の定義
  infix 4 (<=);
  (<=) = { (Int32# x) (Int32# y) -> isTrue# (ge_Int32# y x) };

  infixl 6 (+);
  (+) = { x y -> add_Int32 x y };

  infixl 6 (-);
  (-) = { x y -> sub_Int32 x y };

  -- フィボナッチ数列のn番目の要素を求める関数
  fib = { n ->
    -- ifは関数として定義されている
    -- {}で囲まれた値は遅延評価される
    if (n <= 1)
      { 1 }
      { fib (n - 1) + fib (n - 2) }
  };

  main = {
    print_Int32 (fib 5)
  };
}

Malgoの言語設計

Malgoは、できるだけ言語仕様が小さくなるように設計されている。 これは、実装の負担を軽減するためと、僕個人の好みが主な理由だ。

言語の小ささを表す象徴的な例にif関数がある。 Malgoのifは、次のように関数として定義されている。

if :: Bool -> {a} -> {a} -> a;
if = { True t _ -> !t
     | False _ f -> !f
     };

重要なのはif関数の型Bool -> {a} -> {a} -> aだ。 波括弧{}で囲まれた型の値は、明示的に評価(!演算子)されるまで評価が遅延される。 これによって、ブロックを第一級の値として扱うことが可能になっている。

Malgoコンパイラの実装

MalgoコンパイラLLVMを用いて実装されている。 MalgoのソースコードLLVM IRに変換するわけだが、これは自明な処理ではない。 だいたい次のようなことを考慮する必要がある。

関数値の表現方法

LLVM IRには関数値を表現する方法がない。 Malgoは関数値を多用するので、これは困る。 そこで、クロージャ変換という方法を用いる。

クロージャとは、関数ポインタと自由変数の値の組だ。 例えば、連想配列envinsert関数を使って要素を追加する、 次のような関数を考える。

-- 連想配列envと関数insertは定義済みとする

let insertToEnv = { name val -> insert name val env };

この関数をコンパイルするためには、

  1. 関数にユニークな名前をつける(LLVMでの関数名、関数ポインタの名前になる)
  2. 自由変数を求め、引数に付け加える
  3. 自由変数の値と関数ポインタを組にして、クロージャとする(これがinsertToEnvの実態となる)

また、insertToEnvを関数として呼び出す際は、組を分解して関数ポインタと自由変数の値を取り出し、 取り出した関数ポインタに自由変数の値と本来の引数を引数として与えて呼び出す必要がある。

クロージャ変換の結果、上記のコードは次のように変換される(擬似コード)。

-- insertToEnvの関数部分
define insertToEnv_impl(name, val, captures) {
  insert = captures[0];
  env = captures[1];
  return insert(name, val, env);
}

define ... {
  // insertToEnvの定義
  insertToEnv = (insertToEnv_impl, (insert, env));

  // insertToEnvの呼び出し
  closure_func = insertToEnv[0];
  closure_captures = insertToEnv[1];
  closure_func(name, val, closure_captures);
}

型消去

Malgoはパラメータ多相を持つ。例えば、次のようにしてパイプライン演算子|>を実装できる。

infixl 0 (|>);
(|>) :: a -> (a -> b) -> b;
(|>) = {x f -> f x};

|>には、どんな型のxfでも渡すことができる。 "Hello, world" |> upcase |> putStrLnとか、2 |> { x -> x * 20}のように書ける。

|>LLVM IRで定義するには、型変数abをどのように表現するかを決める必要がある。 型消去は、「すべての型の値が1ワードだと仮定すると、単に型を無視するだけでパラメータ多相を実現できる」というアイディアだ。 Malgoでも型消去を採用している。

型消去を実現するための方法はいくつかあるが、最も単純なやり方は、 符号拡張なりヒープへの割り付けなりで、すべての値を1ワードに収めてしまう完全なボックス化である。 Malgoはユーザー定義型が代数的データ型しか存在しない(構造体とかがない)ので、 プリミティブ型のボックス化だけを定義すればよく、完全なボックス化は自然に実装できる。

型消去により、|>LLVM IRにおける型は次のようになる(ここではクロージャを無視している)。

{ i8*, i8* (i8*, { i8*, i8* (i8*, i8*)* }*)* }* @"|>"(i8* x)

ちょっとごちゃごちゃしているが、引数xの型がi8*になっているのがわかる。 (i8*は、Cにおけるvoid*に相当する)

アンボックス型

前節では、完全なボックス化の実現のためにはプリミティブ型のボックス化を定義すればよいと述べた。 しかし、実際にはMalgo処理系にはボックス化の方法が定義されていない。 プリミティブ型のボックス化は、次のようにユーザー定義の型と関数で実現される。

-- 32bit符号付き整数のボックス表現
data Int32 = Int32# Int32#;

-- ボックス化関数
int32# :: Int32# -> Int32;
int32# = { x -> Int32# x };

Malgoの数値リテラルは、42と書くとint32# 42#に変換される。 42#はボックス化されていない生の整数値であり、多相的な関数に渡すことはできない。 これは、型変数aとプリミティブ型Int32#に、異なるカインドを与えることで実現している。 具体的には、型変数aInt32はカインドTYPE BoxedRepをもち、Int32#はカインドTYPE Int32Repをもつ。 また、BoxedRepInt32RepはカインドRepをもち、TYPERepのみを引数にとる。

内部実装では、型とカインドは同一のものとして扱い、型の型はカインドに、カインドの型は自分自身になるように定義している。 型を総称化する際に、カインドRepをもつ型変数があれば、それを型パラメータにする代わりにBoxedRepを代入する。

TODO

だいぶ本物のプログラミング言語っぽくなってきたが、実用的なレベルには達していない。 実装するべき機能はいろいろとあるが、今ぼんやりと考えているのは

  • レコード型
  • 存在型
  • 型クラス
  • 高度な最適化
  • パッケージ管理システム
  • MLライクなモジュールシステム
  • Algebraic effects
  • 構文木マクロ
  • Language Server

標準ライブラリもかなり貧弱なので、近いうちに気合いを入れてガッと実装してしまいたい。

世の中間言語を集める

個人的なプロジェクトとして、コンパイラ中間言語の設計に取り組んでいる。 今できてるのはラムダ計算に毛が生えてコード生成に向いてるセマンティクスを持つみたいなやつで、『星を継ぐもの』に登場するコリエルというキャラクターから名前をもらってkorielと呼んでいる。

まだまだ未完成で、Haskellで扱うためのライブラリはできたが、ドキュメントもなければバイナリ表現やテキスト表現もない。

github.com

今後korielの開発を進めるに当たって、他の「中間言語」について調べてみようと思った。 プログラミング言語については良く知っているが、言語処理系の中間表現についてはあまり詳しくないからだ。

中間表現と中間言語

中間言語っぽいものを表す言葉は二つある。 「中間表現」"Intermediate representation"と「中間言語」"Intermediate language"だ。 これは混乱を招くので、ちょっと整理しておきたい。

まず、中間表現とは、コンパイラVM*1ソースコードを表現するためのデータ構造だ。単純な文字列ではなく、木構造(ASTとか)やグラフ構造(モジュールの依存関係グラフとか)のように、コンパイラVMにとって都合のいいデータ構造が使われている。

中間表現の中でも、プログラムによって読み書きできるコードの形になっているものを中間言語と呼ぶ。

An IR may take one of several forms: an in-memory data structure, or a special tuple- or stack-based code readable by the program.[3] In the latter case it is also called an intermediate language. Intermediate representation - Wikipedia

C

Cは割と良く使われる中間言語だ。もちろん、Cは汎用プログラミング言語として設計された。しかし、他の言語に比べてアセンブリに近いこと、多くのOS、CPUの上で動作する性能の良い処理系が存在していることから、中間言語として採用されることが多い。

言語Aのコンパイラを作るときに、いきなりA to assemblyなコンパイラを書くのは骨が折れる。そこで、A to Cなコンパイラを書き、あとのことはCコンパイラに任せる、というスタイルがある。

他にも、部分的にコードをCにコンパイルすることで高速化をはかることもある。 Cythonはその好例だ。

LLVM IR

LLVM*2コンパイラのさまざまな機能を提供するツールチェーンだ。 もちろん中間言語もあって、LLVM IRという。 Clang, Rustなど多くのコンパイラで使われている。

Kotlin/Native*3というプロジェクトでは、KotlinをJVMを介さずネイティブなバイナリにコンパイルするが、ここでもLLVMが使われている。

他にも非常に多くのプロジェクトで使われている、デファクトスタンダード的存在である。

SSAをベースとしており、Cのより中間言語向きなバージョンといった雰囲気。

共通中間言語 (CIL)

LLVMデファクトスタンダードである一方で、CILはもっとも「名前は聞いたことがある」中間言語だろう。

C#で使われている中間言語で、.NET対応言語はこの中間言語コンパイルされ、アプリケーションやライブラリはこの中間言語の状態で配布される。 ユーザーの環境にインストールされた.NETのVMがこれを解釈したりJITコンパイルしながら実行される。

パッと調べたところ、例えば以下の記事が詳しい。

ufcpp.net

スタックベースで、オブジェクト指向をサポートするための言語機能が充実している。

JVM byte code

JVMはおそらく(Oracleを信じるなら)30億のデバイスにインストールされている。 JVMが実行するJVM byte codeもそれなりに良く使われる中間言語で、Scala、Kotlin、Groovyなど、JVMで動く言語も多い。RubyにはJVMで走るように実装されたJRubyという実装がある。

これもCILと同様に、スタックベースの言語であり、オブジェクト指向をサポートするための言語機能が充実している。

TODO(追記予定)

残り40GBは流石にまずい二日目

昨日は現状を整理して、要らないアプリをアンインストールしよう、というところまでやった。

アプリのアンインストール

/Applicationsからアプリケーションを削除してはい終了、と行けばいいのだが、~/Library以下にゴミが残ったりする。 これを真面目に探して消すか、それとも放っておくかは悩みどころだが、今回は積極的に消していくことにする。 具体的には、du -h ~/Library | grep 'アプリケーション名'で出てきたそれっぽいフォルダを消す。

  • Adobe Acrobat Reader DC
    • ~/Library/Caches/com.adobe.Readerがそれっぽいので消す。
  • Chrome Remote Desktop Host Uninstaller
    • 実行したら異常終了して消えた。他のChromeアプリももろもろ削除。
  • cluster
    • ~/Library/Application Support/mu.clusterがそれっぽいので消す。
  • FTBApp
    • アップデートした。
  • Godot
    • ~/Library/Application Support/Godotがそれっぽいので消す。
  • IntelliJ IDEA
    • アップデートした。
  • Kindle
    • ~/Library/Caches/com.amazon.kindleがそれっぽいので消す。
  • Logisim
    • 特に何も見つからなかった。
  • mtgaprotracker
    • ~/Library/Application Support/mtgaprotrackerと~/Library/Caches/com.mtgarenapro.mtgaprotrackerと~/~/Library/Caches/com.mtgarenapro.mtgaprotracker.ShipItがそれっぽいので消す。
  • PS Remote Play
    • 特に何も見つからなかった。
  • The Unarchiver
    • ~/Library/Application Scripts/cx.c3.theunarchiverと~/Library/Containers/cx.c3.theunarchiverがそれっぽいので消す。

1GBほど減ったはずだが、微々たるものであまり効果を感じられない。

Xcodeのダイエット

Xcodeが15GBぐらい食ってる。多分シミュレータが場所を取ってるはずなので、いい感じに消す。 xcode coresimulatorとかでググると色々情報がある。

~/Library/Cachesって手で消していいんだろうか

ここまでで93.39GBの空きができた。最初40.3GBだったので、53.09GB削除したことになる。

~/Library/Cachesのサイズは15GBだ。これを消したら68.09GB消したことになる。 調べた感じ、消しても良さそうだったので思い切って消す。ちょっと怖いのでゴミ箱に入れて再起動。 VoiceTriggerだけは消せなかったので残した。

結果

f:id:takoeight0821:20210110170913p:plain
だいぶ減った

で、かなり容量に余裕ができた。 どうもこの画面は起動直後は数値がかなり大きめに出るようで、最初いきなり40GB減ったのはそれが原因だと思う。 まぁ、とりあえずこれで良しとしよう。しかし総容量が371GBというのも心許ない。Bootcamp消そうかな…

残り40GBは流石にまずい一日目

流石にまずいので、この三連休でどうにかします。目標は100GBの削減。

現状

現状。酷い。
現状。酷い。

関心があるのは一番上のMacintosh HD。その内訳は左から順に以下の通り。

  • 書類:91.36GB
  • App:39.39GB
  • iOSファイル:21.12GB
  • ミュージック:6.52GB
  • デベロッパ:5.59GB
  • 写真:2.59GB
  • 音楽制作:2.41GB
  • システム:15.05GB
  • その他:143.32GB

一番はその他、二番が書類、三番がAppといった感じ。

これは大変だぞ…と思いながら書いていたら、いつの間にかこうなってた。

なんか減ってた
なんか減ってた

目を離したすきに  89.03 - 40.3 = 49 でなんと49GBも減っていた! このまま減ってくれないかとしばらく眺めていたが、そんなことはなさそうなので、残り51GBがんばります。

現状をもう一度まとめておくと、以下の通り。

  • 書類:93.7GB
  • App:39.39GB
  • iOSファイル:21.12GB
  • ミュージック:6.52GB
  • デベロッパ:5.59GB
  • 写真:2.59GB
  • 音楽制作:2.41GB
  • システム:15.05GB
  • その他:94.71GB

その他ってなに?

で、その他って具体的にどのファイルたちのことなんだろう。 他はだいたい見当がつく。こんなところだろう。

  • 書類:~(ホームディレクトリ)以下にある、Apple製のアプリケーションが関与していないファイル
  • App:/Applicationsや~/Applicationsに入っている〇〇.app
  • iOSファイル:iPhoneのバックアップ
  • ミュージック:「ミュージック」に入っている音楽。昔はiTunesとか言ってたあれ
  • デベロッパXcodeが使う色々なファイル(エミュレータとか)
  • 写真:iCloudにあげられた写真
  • 音楽制作:GarageBandの使う素材ファイルあたりだろう
  • システム:多分OS本体やそれに類する各種ファイル。下手に触らない方がいい

つまり、その他は上記のファイル「以外」の全て、ということか…

App

まず、Appから手を付ける。いらないアプリが残ってたりしないだろうか。

  • Adobe Acrobat Reader DC.app
    • プレビュー.appで片付く。どうしても必要な時にインストールすればいいだろう。
  • Chrome Remote Desktop Host Uninstaller.app
    • Chrome Remote Desktop一度も使ったことないしとりあえずアンインストールしておこう。
    • 合わせて ~/Applications にあるChrome関連のアプリも調べてみないと
  • cluster.app
    • 頻繁にclusterを利用するわけではないしこれも必要な時で良さそう。
  • FTBApp.app
    • たまにMinecraftのmodpackで遊ぶので残しておこう…と思ったら起動しない。
    • FTBはめちゃくちゃいろんなものをダウンロードするので、メンテが必要そう。
    • とりあえずアプデ。
  • Gobot.app
    • 前ちょっと遊んだけど、それっきりなのでとりあえずアンインストール。
    • ゲーム作りたいな。というかゲームエンジン作りたい。
  • IntelliJ IDEA.app
    • たまに使う。要メンテ。
  • Kindle.app
  • Logisim.app
    • ちょっと前に必要で使ったけど、もう不要なのでアンインストール。
  • mtgaprotracker.app
    • 他のツールに乗り換えたので不要
  • PS Remote Play.app
    • 以前使う必要があったのでインストールしたが、もう不要。
  • The Unarchiver.app
    • なんで入ってるんだ?

ここまでで、要らなさそうなアプリがいくつか見つかった。 明日はまずこれらのアプリを削除したりアップデートしたりする。

半月文章を書き続けた。

11月3日の夜、昔のことを思い出した。 小学校の頃、僕はスポーツ少年団の合宿に参加した。 スポーツをやってたわけではなかった。 空手の師範代だったじいちゃんがその地域のスポーツ少年団に関わってて、 その伝手で参加することになった。

正直なところ全然乗り気じゃなかったし、楽しくもなかった。 でも、友達ができたのは覚えている。もう顔も声も名前も忘れてしまったけど。 どこか白い雰囲気を纏った子だった。

その思い出を文章にした。

この文章を皮切りに、iOSのメモ帳に書いた文章を毎日公開し続けた。 毎日書くのはそんなに大変じゃなかった。 僕のPCにはたくさんの書き散らかされた文章のデータがあり、 それらをサルベージしてスクショ一枚に収まるように整形してやるだけだった。 もちろん書き下ろしもいくつかある。

書いた文章を読んでみると、なんとなく書き手の趣味がわかってくる。 思い出の忘却への恐怖とか、ポストヒューマンとしての人工知能とか、そういうのが好きなんだなってなる。 表現や思想の自由にもうるさいのかも。でも活動家ってわけじゃなさそうだ。

流石にネタが尽きたので、ここ数日は更新頻度が落ちている。 文章を書くのは楽しいので、これからもすこしずつ続けていきたい。

twitter.com