htsign's blog

ノンジャンルで書くつもりだけど技術系が多いんじゃないかと思います

Wandbox のエディタ設定を言語ごとに保存して自動で切り替えする UserScript

表題の通り。
Wandbox さん非常に便利なんですが、言語を切り替えてもエディタ設定は切り替わらないんですよね。

例えば F# はタブ文字をインデントに使えませんが、Go はタブ文字でのインデントを推奨しています。
これら言語を切り替えるときに設定も都度触るのが非常に億劫だと常々思っていました。

というわけで、そのちょっとした不満を解消するために書きました。
設計は「まぁ Proxy 使えば未定義のプロパティも初期化できるやろ」くらいのかなり適当なノリでやってしまったので、メンテが非常につらい感じになっています。
変数の命名も適当です。適当に書いてたら名前が被りそうだったので、普段はオブジェクトをバインドした定数に UPPER_SNAKE_CASE は使わないんですが、他に考えるのも面倒だったので苦し紛れに使っています。

gist.github.com

InoReader 向けに、はてブ integration を追加する UserScript

こんな感じになります。

f:id:htsign:20200511130635p:plain

「動けばいい」をモットーにクソ雑実装。
暇すぎて他にすることなくなったらリファクタリングする可能性が微粒子レベルで存在します。

gist.github.com

Vim script で flatmap

しょーもない話ですが最近になって Vim script を書き始めたので。

全く知らなかったのですが、 Vim 7.0 あたりから map 関数や filter 関数が実装されていた模様。
また、 Vim 8.0 からはラムダ式が、 8.2 からは UFCS っぽいもの1が導入されたそうです。

mapfilter があるなら flatmap もあっていいだろうと。
※ なお、 map filter は新しくリストや辞書を作るのではなく、与えられたオブジェクトそのものを変化させるので、他の言語のノリで使うとハマります。 copy を予め使うといい感じです。2

さて、実装はこんな感じです。

function! FlatMap(obj, body) abort
    let l:privates = {}
    function l:privates.for_list() abort closure
        let l:new_list = []
        for l:x in a:obj
            let l:new_list += a:body(l:x)
        endfor
        return l:new_list
    endfunction
    function l:privates.for_dict() abort closure
        let l:new_dict = {}
        for l:item in values(a:obj)
            for [l:key, l:val] in items(l:item)
                let l:new_dict[l:key] = a:body(l:val)
            endfor
        endfor
        return l:new_dict
    endfunction

    return get({
        \ v:t_list: l:privates.for_list,
        \ v:t_dict: l:privates.for_dict,
    \ }, type(a:obj), { -> v:null })()
endfunction

これで…

" [1, 1, 2, 2, 3, 3]
echo [1, 2, 3]->FlatMap({ x -> [x, x] })

" [1, 2, 3, 2, 4, 6, 3, 6, 9]
echo [
    \ { 'inner': [1, 2, 3] },
    \ { 'inner': [2, 4, 6] },
    \ { 'inner': [3, 6, 9] },
\ ]->FlatMap({ x -> x.inner })

" {'1': '(10)', '2', '(20)', 'bbb': '(BBB)', 'aaa': '(AAA)'}
echo { 'a': { 'aaa': 'AAA', 'bbb': 'BBB' }, '1': { '1': 10, '2': 20 } }
    \ ->FlatMap({ x -> printf('(%s)', x) })

" v:null
echo (100)->FlatMap({ x -> [x, x] })

てな感じで、ほぼほぼ想定通りに動きました。

mapfilter が辞書を引数に取り得るので、今回はそれに倣い辞書の場合も想定してみました。
ただし、特にメリットが分からないので map filter とは違い新たにオブジェクトを作り直しています。

ローカルスコープの関数を定義することができないので、ローカルレベルで辞書を作成して中身に関数を定義することで疑似的にローカル関数を定義するハックを利用しています。


  1. Unified Function Call Syntax の略。 func(arg1, arg2, arg3)arg1->func(arg2, arg3) のように、関数の第一引数のメソッドであるかのように関数呼び出しを書けます。これによってメソッドチェーンの如く数珠繋ぎにフローを書くことができ、可読性が高まります。 Dlang での名称。 Wikipedia(en) には記事がありますが、その他の言語では UFCS が正式な名称ではなさそうなので「っぽいもの」としました。

  2. このことは Vim のマニュアルにも書かれています

scoop cleanup * はいいぞ

Windows を開発機にしているユーザーの100%が導入している1と言われる Scoop ですが、インストールされているアプリケーションのアップデートが溜まってくると割とストレージを圧迫します。

そんなときは scoop cleanup * として過去のバージョンを消してしまうと効果があります。

これで

f:id:htsign:20200127211048p:plain

これが

f:id:htsign:20200127211240p:plain

こうなる。

f:id:htsign:20200127211253p:plain

なお、私の場合は入れているアプリケーションは以下です。

f:id:htsign:20200127211636p:plain

なお、 scoop cache rm * も効果があります。私はやりませんが。
いや別にデメリットがあるわけじゃないんですけどね。

余談ですが、前にいた会社では Scoop 経由で他にもたくさん入れていたのでこれ以上に効果がありました。


  1. 私調べ (N=1)

nim / rust / pony の速度比較

※ 最初に申し上げておきますが、とても浅はかな比較です。

Rust は言わずもがな、「最も愛されているプログラミング言語」4年連続1位1の言語です。
データの生存期間を型で管理することで、GCなしにメモリセーフなプログラミングを可能にしてますね。
トレイトやマクロなどもあり、表現力も豊富で開発も盛んなので注目ですね。
実行速度もちょくちょく C++ に勝ったり負けたりと、非常に高速なことで知られます。

Nim は柔軟な書き方ができる言語で、最近お気に入りです。
ついこの間、めでたく v1.0 を迎えて安定期に入りました🎉
遅延リストのリテラルがないのが残念です。マクロはあるのでその気になれば自力実装はできそうですが、現時点では私には Nim 力が足らず無理です。
Nim で書かれたコードは一度 C のコードに変換されてから2、C のコンパイラでさらにネイティブコードにコンパイルされます。

Pony は昨日3初めて知った言語ですが、割と前からあるみたいですね。
言語構文レベルでアクターモデルを採用しているのが、たぶん最大の特徴です。
Rust に似て変数の所有権のようなもの4を型に持たせることで、アクターモデルでありながらデータのコピーを極力減らすことができ、高速かつ安全なプログラミングを可能にしているようです。
補完にまで対応した IDE やエディタ拡張が見当らないので、それがネックですね…。
まだセルフホストには至っていない模様。

環境

WSL Ubuntu on Windows 10

$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.3 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.3 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic
$ rustc --version
rustc 1.39.0 (4560ea788 2019-11-04)
$ nim --version
Nim Compiler Version 1.0.2 [Linux: amd64]
Compiled at 2019-10-22
Copyright (c) 2006-2019 by Andreas Rumpf

git hash: 193b3c66bbeffafaebff166d24b9866f1eaaac0e
active boot switches: -d:release
$ ponyc --version
0.33.0-98c36095 [release]
compiled with: llvm 7.0.1 -- cc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
Defaults: pic=true

比較

なるべく公平になるようにコードを書いているつもりです。

FizzBuzz

コード
Rust
fn fizz_buzz(n: i32) {
  let s =
    match (n % 3, n % 5) {
      (0, 0) => "FizzBuzz".to_string(),
      (0, _) => "Fizz".to_string(),
      (_, 0) => "Buzz".to_string(),
      _ => n.to_string(),
    };
  println!("{}", s);
}

fn main() {
  for x in 1..=100 {
    fizz_buzz(x);
  }
}
Nim
proc fizzBuzz(n: int) =
  let (x, y) = (n mod 3, n mod 5)
  let s =
    if (x, y) == (0, 0): "FizzBuzz"
    elif x == 0: "Fizz"
    elif y == 0: "Buzz"
    else: $n
  echo s

for x in 1..100:
  fizzBuzz x
Pony
use "collections"

actor FizzBuzz
  let _env: Env
  
  new create(env': Env) =>
    _env = env'
  
  be fizz_buzz(x: I32) =>
    let s =
      match (x % 3, x % 5)
      | (0, 0) => "FizzBuzz"
      | (0, _) => "Fizz"
      | (_, 0) => "Buzz"
      else x.string() end
    _env.out.print(s)

actor Main
  new create(env: Env) =>
    let fb = FizzBuzz(env)
    for x in Range[I32](1, 101) do
      fb.fizz_buzz(x)
    end
コンパイル速度
Rust
$ time -p rustc -Copt-level=3 -Clto -Cpanic=abort fizzbuzz.rs
real 2.23
user 1.96
sys 0.39
Nim
$ time -p nim c -d:release --opt:speed --hints:off fizzbuzz
CC: stdlib_io.nim
CC: stdlib_system.nim
CC: fizzbuzz.nim
real 2.03
user 1.70
sys 0.48
Pony
$ time -p ponyc --verbose=0
real 2.84
user 2.03
sys 0.81
Rust Nim Pony
real 2.23 2.03 2.84
user 1.96 1.64 2.12
sys 0.39 0.54 0.70

コンパイル速度はそこまで差が出ないようです。
※ ただし、Nim はコンパイル結果をキャッシュするので、同じプロジェクトであれば2度目以降のコンパイルが非常に高速になります。 --forceBuild / -f オプションを付けるかキャッシュを削除すると1度目と同じ条件になります。今回は毎回キャッシュを削除しました。
※ Rust は最適化オプションをいくつか外すことで、成果物のファイルサイズを犠牲(3倍近く)にかなり高速(4分の1以下)になりました。

ファイルサイズ
Rust
$ ls -l fizzbuzz
-rwxrwxrwx 1 htsign htsign 928400 11月 21 21:58 fizzbuzz*
Nim
$ ls -l fizzbuzz
-rwxrwxrwx 1 htsign htsign 88864 11月 21 21:59 fizzbuzz*
Pony
$ ls -l fizzbuzz
-rwxrwxrwx 1 htsign htsign 163176 11月 21 21:59 fizzbuzz*
Rust Nim Pony
928,400 bytes (907KB) 88,864 bytes (87KB) 163,176 bytes (159KB)

※ KB 表記の端数は四捨五入

Rust が圧倒的にデカいですね。
Rust のバイナリがデカい理由はデバッグシンボルが残っているからかと思われます。最適化オプション有効にしてるのに…。
ここではコマンド単体で完結することを前提とするのでこれを最終結果とします。

実行速度
Rust
$ time -p ./fizzbuzz > /dev/null
real 0.01
user 0.00
sys 0.01
Nim
$ time -p ./fizzbuzz > /dev/null
real 0.00
user 0.00
sys 0.00
Pony
$ time -p ./fizzbuzz > /dev/null
real 0.02
user 0.00
sys 0.01
Rust Nim Pony
real 0.01 0.00 0.02
user 0.00 0.00 0.00
sys 0.01 0.00 0.01

まぁ当然ですが、負荷が軽すぎてほとんど差が出ないですね…。

45番目のフィボナッチ数

※ 45番目なのは計算時間的にちょうどよさそうなので。

コード
Rust
fn fibonacci(n: i64) -> i64 {
  match n {
    1 | 2 => 1,
    _ => fibonacci(n - 2) + fibonacci(n - 1),
  }
}

fn main() {
  println!("{}", fibonacci(45))
}
Nim
func fibonacci(n: int64): int64 =
  case n
  of 1, 2: 1.int64
  else: fibonacci(n - 2) + fibonacci(n - 1)

echo fibonacci(45)
Pony
primitive Fibonacci
  fun fib(n: I64): I64 =>
    match n
    | 1 | 2 => 1
    else fib(n - 2) + fib(n - 1) end

actor Main
  new create(env: Env) =>
    env.out.print(Fibonacci.fib(45).string())
コンパイル速度

※ 実行コマンドは FizzBuzz のとほとんど変わらないので割愛

Rust Nim Pony
real 2.20 2.00 1.19
user 1.75 1.64 0.89
sys 0.45 0.48 0.29

なぜか Pony が FizzBuzz の場合と比べてちょっと速くなりました。

ファイルサイズ
Rust Nim Pony
924,024 bytes (902KB) 84,288 bytes (82KB) 158,696 bytes (155KB)

こちらも相変わらず Rust がダントツにデカいです。

実行速度
Rust Nim Pony
real 2.88 3.45 3.52
user 2.87 3.45 3.48
sys 0.01 0.00 0.03

さっきと比べて少し差が出ましたね。
Rust、やはり速い。

並列性を上げて計算すれば Pony も速くなったりするのでしょうか。


以上、Pony はまだまだ発展途上ですし、Rust もバージョン上がるごとに最適化が進んだりするので、本当にガバガバ比較です。
結局のところ好みで選べばいいんじゃないかと思います。
並列計算が目的なら Erlang や Elixir などの選択肢もあります。が、あれはプロセス単位での並列なのでちょっと事情が違うかも。


  1. https://insights.stackoverflow.com/survey/2019#technology-_-most-loved-dreaded-and-wanted-languages

  2. いわゆるトランスパイル

  3. 投稿時間が日を跨いでしまったので…。

  4. Pony では Reference Capabilities と呼ばれます。