無駄と文化

実用的ブログ

Grass言語でHello,world!に挑戦

Grass 言語というプログラミング言語があります。

esolangs.org オリジナルの製作者 (UENO Katsuhiro さん) のサイトが2025年1月頃を最後に消えているので参照先が海外の Wiki しかない...

今回は Grass 言語に入門してみます。まずは Hello,world! を表示するのが基本の "き" ですね。

Grass 言語で Hello,world! に挑戦

Hello,world! を書くぞ!

wwwwWWWWwwWWWWwwwWwwwWWWwvwwwWWwWWWWwvwwWWwWWWwvwwWWwWWWwWWWWwvwwWWwWWWwWWWWwWWW
WWwWWWWWWwvwWWWWWWwwwwWwwwvWwWWWWwwwwwvwWWWWWWWWWwwwwwWwwwwwwwWWWWWWWWWWWwWwwwww
wwwwvWwvwWWWWWWWWWWWwwWwwwvWwvwWWWWWWWWWWWWwwwwwwwwwwwWwwwwwwwwwwwwWWWWWWWWWWWWW
WwWwwwwwwwwwwvWwvwWWWWWWWWWWWWwwwwwwwwwwwWWWWWWWWWWWWWWWWwWwwwwwwvWwvwWWwwwwwwww
wwwwwwwwwwwWwwwwwwwwwwwwwwwwwwwwwvwWWwWWWWWWWWWWWWWWWWWWWWWwWWWWWWWWWwwwwwwwwwww
wwwwwwwwwwwWwwvwWWwWWWWWWWWWWWwwwwwwwwwwwwwwwwwwwwwwWwwvwWWwWWWWWWWWwwwwwwwwwwww
wwwwwwwwwwwWwwvwWWwWWWWWWWWWwwwwwwwwwwwwwwwwwwwwwwwwWwwvwWWwWWWWWWWWWWWWWWWWWWWW
WWWWWwvwWWWwWWWWWWWWWWWWWWWWWwwwwwwwwwwwwwwwwwwwwwwwwwwWwwvwWWwWWWWWWWWWWWWWWWWW
WWWWWwwwwwwwwwwwwwwwwwwwwwwwwwwwWwwvwWWwWWWWWWWWWWWWWWWWWWWWWWWwwwwwwwwwwwwwwwww
wwwwwwwwwwwWwwvwWWWWWWWWWWWWWWWWWWWWWWWWWWWwvwWWWWWWWWwWWWwWWWWWWWWwWWWWWwWWWWWW
WWWwWWWWWWWwWWWWWWWWwWWWWWWWWWWWwWWWWWWWWWWwWWWWWWWWWWWWWWWWWWwWWWWWWWWWWWWwWWWW
WWWWWWWWWwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwWWWWWWWWWWWWWWwwwwWWWWWWWWWWWW
WWWWwWWWWWWWWWWWWWWWWwWWWWWWWWWWWWWWWWWwwwwwwwwwWWWWWWWWWWWWWWWWWWWWWWWwWWWWWWWW
WWWWWWWWWWWwWWWWWWWWWWWWWWWWWWWWWWWWWWWWwWWWWWWWWWWWWWWWWWWWWWwWWWWWWWWWWWWWWWWW
WWWWWWWWWWWWWWwWWWWWWWWWWWWWWWWWWWWWWWw

よし書いた。

実行してみよう。

$ npx @susisu/grass-js helloworld.minify.grass 
Hello,world!

ヨシ! 👈⛑️

 

何がどうなっている?

これでは何もわからないと思うので、インデントやコメントを入れたバージョンを置いておきます。

// In
// _grass_
// Succ
// Out

// λm.λn.λf.λx.m f (n f x)
wwww WWWWww WWWWwww Wwww WWWw                                                   v // add
// λm.λ.n.λf.m (n f)
www WWw WWWWw                                                                   v // times

// λf.λx.f (f x)
ww WWw WWWw                                                                     v // 2
// λf.λx.f (f (f x))
ww WWw WWWw WWWWw                                                               v // 3
// λf.λx.f (f (f (f (f x))))
ww WWw WWWw WWWWw WWWWWw WWWWWWw                                                v // 5

w
  WWWWWW wwww /* add 2 */
  W      www  /* _   5 */                                                       v // const 7
Ww                                                                              v // 7
WWWW wwwww /* 3 2 = 2^3 => 8 */                                                 v // 8
w
  WWWWWWWWW   wwwww     /* add 5 */
  W           wwwwwww   /* _   3 */
  WWWWWWWWWWW w         /* add _ */
  W           wwwwwwwww /* _   3 */                                             v // const 11
Ww                                                                              v // 11
w
  WWWWWWWWWWW ww  /* add 11 */
  W           www /* _   11 */                                                  v // const 22
Ww                                                                              v // 22
w
  WWWWWWWWWWWW   wwwwwwwwwww  /* times 2 */
  W              wwwwwwwwwwww /* _     2 */
  WWWWWWWWWWWWWW w            /* times _ */
  W              wwwwwwwwww   /* _     7 */                                     v // const 28
Ww                                                                              v // 28
w
  WWWWWWWWWWWW     wwwwwwwwwww /* 3    5 */
  WWWWWWWWWWWWWWWW w           /* add  _ */
  W                wwwwww      /* _   22 */                                     v // const 147
Ww                                                                              v // 147

w
  WW wwwwwwwwwwwwwwwwwww   /* 147 Succ    */
  W  wwwwwwwwwwwwwwwwwwwww /* _   _grass_ */                                    v // const \n
w
  WW                      w                      /* \n              */
  WWWWWWWWWWWWWWWWWWWWW   w                      /* Succ \n   => VT */
  WWWWWWWWW               wwwwwwwwwwwwwwwwwwwwww /* 22   Succ       */
  W                       ww                     /* _    VT   => !  */          v // const !
w
  WW          w                      /* !            */
  WWWWWWWWWWW wwwwwwwwwwwwwwwwwwwwww /* 11 Succ      */
  W           ww                     /* _  !    => , */                         v // const ,
w
  WW       w                       /* ,            */
  WWWWWWWW wwwwwwwwwwwwwwwwwwwwwww /* 28 Succ      */
  W        ww                      /* _  ,    => H */                           v // const H
w
  WW          w                        /* H            */
  WWWWWWWWW   wwwwwwwwwwwwwwwwwwwwwwww /* 28 Succ      */
  W           ww                       /* _  H    => d */                       v // const d
w
  WW                        w /* d */
  WWWWWWWWWWWWWWWWWWWWWWWWW w /* Succ _ => e */                                 v // const e
w
  WWW               w                          /* d           */
  WWWWWWWWWWWWWWWWW wwwwwwwwwwwwwwwwwwwwwwwwww /* 8 Succ      */
  W                 ww                         /* _ d    => l */                v // const l
w
  WW                     w                           /* l           */
  WWWWWWWWWWWWWWWWWWWWWW wwwwwwwwwwwwwwwwwwwwwwwwwww /* 3 Succ      */
  W                 ww                               /* _ l    => o */          v // const o
w
  WW                      w                            /* o           */
  WWWWWWWWWWWWWWWWWWWWWWW wwwwwwwwwwwwwwwwwwwwwwwwwwww /* 3 Succ      */
  W                       ww                           /* _ o    => r */        v // const r

w
  WWWWWWWWWWWWWWWWWWWWWWWWWWW w /* Out _arg1_ */                                v // Out

// main
w
  WWWWWWWW                        w                                             /* H           */
  WWW                             w                                             /* Out H       */
  WWWWWWWW                        w                                             /* e           */
  WWWWW                           w                                             /* Out e       */
  WWWWWWWWW                       w                                             /* l           */
  WWWWWWW                         w                                             /* Out l       */
  WWWWWWWW                        w                                             /* Out l       */
  WWWWWWWWWWW                     w                                             /* o           */
  WWWWWWWWWW                      w                                             /* Out o       */
  WWWWWWWWWWWWWWWWWW              w                                             /* ,           */
  WWWWWWWWWWWW                    w                                             /* Out ,       */
  WWWWWWWWWWWWW                   wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww     /* Out _grass_ */
  WWWWWWWWWWWWWW                  wwww                                          /* Out o       */
  WWWWWWWWWWWWWWWW                w                                             /* r           */
  WWWWWWWWWWWWWWWW                w                                             /* Out r       */
  WWWWWWWWWWWWWWWWW               wwwwwwwww                                     /* Out l       */
  WWWWWWWWWWWWWWWWWWWWWWW         w                                             /* d           */
  WWWWWWWWWWWWWWWWWWW             w                                             /* Out d       */
  WWWWWWWWWWWWWWWWWWWWWWWWWWWW    w                                             /* !           */
  WWWWWWWWWWWWWWWWWWWWW           w                                             /* Out !       */
  WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW w                                             /* \n          */
  WWWWWWWWWWWWWWWWWWWWWWW         w                                             /* Out \n      */

解説は諦めます。

 

補足

今回書いたコードをここに置いておきます。

 

プログラムの実行には id:susisu さんの JavaScript 実装を使いました。npx で実行すればインストールも何も不要で便利。

github.com

 

Grass 言語の言語仕様や動作原理については公式サイトが詳しいです。
でも消えてしまっているので Wayback Machine などを漁る必要あり。

web.archive.org

 

まとめ

そろそろ Grass 言語を理解したいと思い立って勉強してみました。めちゃくちゃ大変だった...。

 

 

私からは以上です。

おまけ

そのほかの Hello,world! シリーズはこちら

blog.mudatobunka.org

combineではParserのタプルもまたParserになる

自然数の和と積を表現する構造体を考えよう。

#[derive(Debug, PartialEq)]
enum Term {
    Sum(usize, usize),
    Prod(usize, usize),
}

このような素朴な enum を用意する。
数式をパースして Term を得るようなパーザを作りたい。

combine というパーザコンビネータのライブラリがあるのでこれを使う。

docs.rs

自然数のパーザや演算子のパーザは1行で書ける。
自然数は '0' ~ '9' の文字の1回以上の連なりと定義できる。演算子は '+', '*' のいずれかだ。

use combine::parser::char::{Parser, one_of, parser::char::digit};

fn number<'a>() -> impl Parser<&'a str, Output = usize> {
    // 自然数は digit の1回以上の連なり
    // つづいて String としての "123" を usize としての 123 に変換する
    many1(digit()).map(|s: String| s.parse::<usize>().unwrap())
}

fn operator<'a>() -> impl Parser<&'a str, Output = char> {
    // 演算子は '+' か '*' のいずれか
    one_of("+*".chars())
}

これらを組み合わせて Term のパーザーを作ろう。

 

例えば文字列 "2*3" をパースするとしたら「number(), operator(), number() を立て続けに適用するパーザ」を作ればよさそうだ。
この「複数のパーザを立て続けに適用するパーザ」を表現したいときどう書けるか。

実はパーザをタプルで包むだけでいい。

fn term<'a>() -> impl Parser<&'a str, Output = (usize, char, usize)> {
    (number(), operator(), number())
}

たったこれだけ。 Parser<Outout=T1>, Parser<Outout=T2>, ..., Parser<Outout=Tn> のタプルは Parser<Output=(T1, T2, ..., Tn)> になる。

 

(number(), operator(), number()) 自体がパーザなので、いつものメソッドが使える。例えば .map() で結果を変換できる。

fn term<'a>() -> impl Parser<&'a str, Output = Term> {
    (number(), operator(), number())
        .map(|(lhs, op, rhs): (usize, char, usize)| match op {
            '+' => Term::Sum(lhs, rhs),
            '*' => Term::Prod(lhs, rhs),
            _ => unreachable!(),
        })
}

 

パーザが書けたので、実際にパースする処理を書くのは簡単。

fn parse_term(s: &str) -> Option<Term> {
    match term().parse(s) {
        Ok((term, _)) => Some(term),
        Err(_) => None,
    }
}

#[test]
fn test_parse_term() {
    assert_eq!(parse_term("12+34"), Some(Term::Sum(12, 34)));
    assert_eq!(parse_term("7*8"), Some(Term::Prod(7, 8)));
    assert_eq!(parse_term("7/8"), None);
}

いい感じ。

 

combine には .and() メソッドもある

「複数のパーザを立て続けに適用するパーザ」を表現するには他にも方法がある。

.and() メソッドを使えば、 p1.and(p2) のようにして p1, p2 を立て続けに適用するパーザを表現できる。

use combine::parser::char::char;

fn negative_number<'a>() -> impl Parser<&'a str, Output = isize> {
    char('-').and(number()).map(|(_, n)| -(n as isize))
}

p1: Parser<Output=T1>, p2: Parser<Output=T2> に対して p1.and(p2)Parser<Output=(T1, T2)> になる。
(p1, p2)p1.and(p2) は同じ振る舞いになる。

 

ところがパーザが3つ以上連なるときに .and() を使って合成しようとすると、結果は少しいびつなものになる。

fn term<'a>() -> impl Parser<&'a str, Output = ((usize, char), usize)> {
    number().and(operator()).and(number())
    // メソッドチェーンで綺麗に書ける
    // しかし結果は (usize, char, usize) ではなく ((usize, char), usize) になる
}

.and() は呼び出しごとに一組ずつタプルで包むので、結果は ((T1, T2), T3) になる。もし4つのパーザを合成しようとすれば、結果は (((T1, T2), T3), T4) になるはずだ。
タプルがネストしてしまうのは少々ツラい。

一方、パーザをタプルで書き並べる形なら、結果がフラットで読みやすい。

fn term<'a>() -> impl Parser<&'a str, Output = (usize, char, usize)> {
    (number(), operator(), number())
    // 結果は (usize, char, usize) になる
}

 

タプルに対してトレイトを impl する

タプルに対して何らかのトレイトを実装するのは Rust では一般的なことだ。
combine の実装をサラリと見ておこう。

combine/src/parser/sequence.rs

tuple_parser!(PartialState1; A);
tuple_parser!(PartialState2; A, B);
tuple_parser!(PartialState3; A, B, C);
// ...
tuple_parser!(PartialState20; A, B, C, D, E, F, G, H, I, J, K, L, M, N, P, Q, R, S, T, U);

1要素のタプルから20要素のタプルまで、定義がずらりと並んでいる。

 

タプルに対してトレイトを impl する (カスの応用)

私がお気に入りのトレイトである From トレイト を例にする。

#[derive(Debug)]
struct Number {
    value: i32,
}

impl From<i32> for Number {
    fn from(item: i32) -> Self {
        Number { value: item }
    }
}

fn main() {
    let num = Number::from(30);
    println!("My number is {:?}", num);
    // => My number is Number { value: 30 }
}

自作の構造体 Number に対して From<i32> トレイトを impl することで Number::from(42) が呼び出し可能になる。

From<i32> を impl するだけで、Into トレイトも使用可能になって、let num: Number = 42.into(); のように書ける。これも便利。

 

ところで From トレイトは1引数のメソッドしかサポートしていないのが不便だったりする。
そこでタプルに対して From トレイトを impl することで擬似的に多引数に対応できる。

impl From<(i32, i32)> for Number {
    fn from((m, n): (i32, i32)) -> Self {
        Number { value: m * n }
    }
}

fn main() {
    let num = Number::from((30, 40));
    println!("My number is {:?}", num);
    // => My number is Number { value: 1200 }
}

Number::from() がまるで2引数関数のように呼べるようになった。

 

まとめ

combine にて、タプルに対してトレイトを impl するテクニックの綺麗な応用が見られて感激。

 

 

私からは以上です。

ドラマ『ロキ』感想 - なぜケイシーは魚を知らないのか

ドラマ『ロキ』の1期を見返した。何度見返しても新しい発見がある。

魚を知らないケイシー

ドラマ『ロキ』はロキが四次元キューブ (Tesseract・スペースストーン) を拾い上げるところから始まる。2012年のニューヨークの戦いのあと四次元キューブの力で逃亡したところをミニットメンに拘束され、TVA 本部に連行される。
連行後、持っていた四次元キューブは TVA の事務員の手に渡って保管されることになる。その事務員がケイシーだ。

ところで1話でロキとケイシーがこのような会話をしている。

ロキ:キューブをよこさないと魚みたいに捌くぞ

ケイシー:魚って何?!

ロキ:なんで魚が分からない!

ケイシー:ずっとこのデスクの裏ですごしてきた。どういう脅しなのか従う前に知っておきたい

続くシーンではケイシーが別のスタッフに事情を話すシーンがあり、そこでも魚について言及している。

ケイシー:僕を魚に変えるとか言って脅すんですよ。魚って何か知りませんけど

どうやらケイシーは魚を知らないようだ。
これは文化の違いによって言葉が通じなかったとかではなく。ケイシーは本当に魚を見たことがないし、「魚」という概念が知識の中にないと思われる。

一方で分析官やミニットメンは博識

TVA 本部が現実離れした空間で、スタッフ全員が魚を知らないのかというとそうでもないようだ。
分析官メビウスとロキが災害について調べるシーンではこのような会話がある。

ロキ:2050年にはツバメが絶滅している

メビウス:ああ、生態系がめちゃくちゃになった

明らかにツバメという生き物を知ったうえで会話が成り立っている。

 

またミニットメンが「時間軸の全言語を話せる」と発言するシーンもある。
分析官やミニットメンは分岐時間軸におもむいて現地の人々に聞き込み・尋問をするようだから、当然、各時代のさまざまな地域の言語に精通していなければいけないということだろう。*1
言語を習得するには文法や発音だけでなく語彙も必要だから、分析官やミニットメンは「魚」「ツバメ」などの物事は当然知っていると推測できる。

なぜケイシーは魚を知らないのか

ケイシーが魚を知らない理由。それは "業務に必要ない知識だから" だと思われる。
事務員であるケイシーは没収した物品の保管や書類の整理が仕事で、神聖時間軸や分岐時間軸についての知識は不要。TVA の職員は誰しも分岐時間軸から攫われてきた変異体だが、記憶の消去・洗脳・教育などする過程で不要な知識は消されているんだろう。*2
ともかく TVA のスタッフは各自の業務に必要な知識しか与えられておらず。ケイシーの場合は神聖時間軸や分岐時間軸についても興味がないように見える。そこがどんな世界でどんな物が在るのか、思いを馳せてもいないようだ。

でもケイシーは TVA ガイドブックオタクで、無知って感じじゃないよね

ドラマ『ロキ』2期で明らかになるが、ケイシーは TVA ガイドブックを "ほぼ暗記するほど" 読み込んでいて TVA の装置や設備にはめちゃくちゃに詳しい。それでもケイシーの興味は TVA の内部 (装置や設備) にだけ向いていて、それが制御している時間軸には向いていない。
このような「興味の方向」も洗脳・教育の成果なのか?そうかも知れないが、そうでないかも。

というのは、そのような興味の限られた変異体だけが TVA のスタッフとして受け入れられているかもしれないからだ。それ以外の不適格だと判断された変異体は時間犯罪者として剪定されていると考えられる。むしろそれが大部分だ。
好奇心が強すぎる人間は TVA のスタッフにはなれない。ケイシーは時間軸に興味がないからこそ今のポジションで働けている。

変異体スカウト機関としての TVA

TVA の本当の役割は、時間軸の管理というより変異体の選別とスカウトであるように思える。
2期の終盤に「分岐時間軸が増えすぎると時間織り機は神聖時間軸以外の時間軸を自動的にリセットするように働く」ということが明かされる。これはつまり分岐の検出や時間軸のリセットは完全自動化が可能で、TVA のスタッフが個別に対応する必要なんて無いということだ。
分岐の原因になった変異体をわざわざ連行して裁判にかける必要も無い。時間軸をリセットすれば、そこにいる変異体ごと消え去るはずだ。

ではなぜ TVA は存在し、変異体を捕まえるのか。それは有用な変異体を見極めてスカウトするためだと思われる。
TVA のスタッフは誰もが分岐時間軸から連れてこられた変異体だ。神聖時間軸を守ることが目的なので、神聖時間軸から人を連れてくることはできない。スタッフ候補は分岐時間軸から来た変異体に限られることになる。
TVA の裁判は変異体を裁いて殺すためではなく、面接してスカウトするためにある。(2012年のニューヨークから来たロキをメビウスがスカウトしたように)

この裁判という名の面接・スカウトの過程で、TVA の運営に不都合な人材は排除されていると思われる。馬鹿すぎてもいけないが信念が強すぎたり好奇心・探究心が強すぎてもダメだ。
ケイシーが「神聖時間軸にはどんな生き物がいるんだろう?海に暮らしている魚ってなんだろう?」と気になってこっそり資料を漁るようなタイプなら、おそらく不適格人材として虚無空間行きになっている。これがケイシーが魚を知らない理由だ。

情報の統制・制御された無知

ケイシーをはじめとして一部のスタッフが無知なのは、制御されたものだと考えられる。情報は統制されていて、必要以上に知らせないよう司書によって管理されている。実際、ロキが『時間の始まりと終わりに関する資料が見たい』と要望したときに司書に「それは極秘です」と突っぱねられている。

資料だけでなく、現地視察も厳しく制限されていると思われる。タイムドアで神聖時間軸・分岐時間軸に出ていくことが許可されているのは命令を受けた分析官とミニットメンに限られている雰囲気だ。
注意して見ていると、2期のあの TVA が混乱している時期においてさえ、ケイシーが時間軸に出ていく描写は無い。ガイドブックオタクでタイムパッドの仕組みに精通しているケイシーなら、タイムドアの作り方・時間軸への接続の仕方は知っているはずだ。それでも時間軸に出ていくことをしないのは「TVA 内部で働くのが自分の役割」という意識が強いからだろう。

TVA を嫌悪するシルヴィ

言うまでもなくシルヴィは TVA を嫌悪している。それはかつて TVA が自分を捕らえて選定しようとしたから。それも事実だが、それだけではないようだ。

シルヴィの目的は TVA を乗っ取ることではなく TVA を完全に解体することだった。誰にも追われずに生きられる分岐時間軸を守るため。自分のためだけでなく、同じ境遇にある他の変異体にも自由を与える意思が根底にある。
裏返しに、シルヴィが TVA を憎むのは多くの変異体から自由を奪い、多くの分岐時間軸をリセットして、そこに暮らす人たちの命を奪ってきたからだ。その嫌悪は TVA スタッフたちの無知にも向けられている。つまり、自分たちがリセットしている世界がどのようなものなのか、自分たちの行いが当事者 (変異体) にとってどんな結果を生むのかを "知ろうとせずに" 手を下していることに対する嫌悪だ。

とはいえ、スタッフ目線だと制御された無知に対して嫌悪を向けられてもどうしようもないのだが。

『ロキ』の物語は「追われる立場 (ロキ・シルヴィ)」と「自由 (追われずに生きる)」という二項対立にも読めるし、「制御された無知 (TVA スタッフ)」と「自由 (本来の人生)」の対立にも読める。
シルヴィの能力は虚像を作って人を騙すのではなく、その人が忘れている古い記憶にアクセスし思い出させる (= 知識を与える) ものだ。無知な TVA スタッフに対して外の世界を知らせることで自由意志を求める思いを呼び起こし、スタッフたちを制御された無知から救おうとする姿を象徴しているのだろう。

まとめ

ケイシーが魚を知らないところから、 TVA における制御された無知、無知からの開放によって TVA スタッフを救おうとするシルヴィというテーマを掘り下げてみた。
見返すたびに隠された対比やテーマに気付けておもしろい。

 

 

私からは以上です。

*1:TVA はタイタン人やクリー人やヴァンパイアとも会話するらしく、宇宙全体の言語をすべて話せるのは冷静に考えて凄すぎる

*2:逆に分析官やミニットメンが「時間軸の全言語を話せる」のは業務に必要な知識として脳に流し込まれている可能性もある