ActiveRecord で実行される sql を確認する

ActiveRecord で実行される sql を確認したい場合、 #to_sql メソッドが利用できます。

# このリレーションでレコードを読み込む場合に実行される SQL を文字列で取得する
p Blog.joins(:articles).where(articles: { name: "hoge" }).to_sql
# => "SELECT \"blogs\".* FROM \"blogs\" INNER JOIN \"articles\" ON \"articles\".\"blog_id\" = \"blogs\".\"id\" WHERE \"articles\".\"name\" = 'hoge'"

また、 puts で出力する場合は "エスケープされないのでより読みやすくなります。

puts Blog.joins(:articles).where(articles: { name: "hoge" }).to_sql
# => SELECT "blogs".* FROM "blogs" INNER JOIN "articles" ON "articles"."blog_id" = "blogs"."id" WHERE "articles"."name" = 'hoge'

ActiveSupport::TimeWithZone#+ に数値を渡したときの挙動

ActiveSupport::TimeWithZone#+ に値を渡すと『渡した時間を加算した時刻』を返します。

# 現在の時刻
current_time = Time.current
pp current_time                # => Mon, 06 Jan 2020 20:10:15 JST +09:00

# + 1日
pp current_time + 1.days      # => Tue, 07 Jan 2020 20:10:15 JST +09:00

# + 10秒
pp current_time + 10.seconds  # => Mon, 06 Jan 2020 20:10:25 JST +09:00

ActiveSupport::TimeWithZone#+ に数値を渡したときの挙動

ActiveSupport::TimeWithZone#+ に数値を渡した場合、数値を秒数として計算します。

# 現在の時刻
current_time = Time.current
pp current_time          # => Mon, 06 Jan 2020 20:11:28 JST +09:00

# + 10.seconds と同じ意味
pp current_time + 10     # => Mon, 06 Jan 2020 20:11:38 JST +09:00

これ、意図としてわかりづらいので数値はエラーにしてしまったほうがいいんじゃないですかねー。

Vim 8.2 で Vim script に -> 演算子が追加された

Vim 8.2 で Vim script に -> 演算子が追加されました。
これは f(x, y)x->f(y) という風に書くための演算子になります。
例えば

echo map(filter(range(1, 10), { -> v:val % 2 == 0 }), { -> v:val * v:val })

というようなネストしている関数呼び出しを

echo 1
\   ->range(10)
\   ->filter({ -> v:val % 2 == 0 })
\   ->map({ -> v:val * v:val })

のように記述する事ができます。
これはユーザ定義関数でも利用する事ができます。

function! s:plus(a, b)
    return a:a + a:b
endfunction

echo 1->s:plus(2)->s:plus(3)

便利。

注意

組み込み関数によっては f(x, y)x->f(y) ではなくて y->f(x) になる場合があるので注意する必要があります。
例えば printf(fmt, a)a->printf(fmt) となります。

echo 42->printf("%04x")
" output: 002a

関数によって -> の挙動が異なるの、マジで頭がおかしいと思うのでどうにかしてほしい…なんでこんなひどい仕様にしてしまったのか…。
こういうのはちゃんと統一性を保ってほしいんですがどうにかなりませんかね…。
やっぱり Vim script はクソ

Vim 8.2 で Vim script の辞書のキーの定義が簡略化できるようになった

Vim script で辞書を定義する場合、次のようにキーを文字列リテラルで定義していました。

let dict = { "one" : 1, "two" : 2, "three" : 3 }
echo dict
" => {'one': 1, 'two': 2, 'three': 3}

Vim 8.2 では上記のような定義を以下のように定義する事ができます。

" #{ から始めることで `キー名: 値` で定義できる
let dict = #{ one: 1, two: 2, three: 3 }
echo dict
" => {'one': 1, 'two': 2, 'three': 3}

こんな感じで辞書リテラル#{ から始めることで キー名: 値 で定義できるようになります。
キー名には ASCII文字 + 数字 + - + _ が使用できます。

let dict = #{ one: 1, two_key: 1, three-key: 2, 444: 4 }
echo dict
" => {'one': 1, 'three-key': 2, '444': 4, 'two_key': 1}

また 444: 4 とした場合、キーは "444" になるので注意する必要があります。
これ自体は便利なんですがなぜ #{} なんていうクソダサいリテラルにしてしまったのか…。もっといい書き方はなかったんですかねえ。

C++20 で指示付き初期化子を使ったキーワード引数

C++20 で指示付き初期化子が実装されました。
指示付き初期化子はクラスを初期化する時に『任意のメンバ変数』を指定して初期化する事ができます。

struct pos{
    int x;
    int y;
    int z;
};

// .メンバ変数名 = で初期化できる
pos p{ .x = 1, .y = 2, .z = 3 };

これを利用してキーワード引数っぽい関数を定義してみました。

#include <iostream>

const auto disp = [] {
    // 引数を受け取るクラスを定義しておく
    // クラス定義を公開したくないので致し方なくクロージャ化
    struct pos_t { int x; int y; int z = 0; };

    return [](pos_t pos) {
        std::cout << "x = " << pos.x << std::endl;
        std::cout << "y = " << pos.y << std::endl;
        std::cout << "z = " << pos.z << std::endl;
    };
}();

int
main(){
    // 指示付き初期化子を利用してキーワード引数っぽく渡す
    disp({ .x = 1, .y = 2 });
    std::cout << "---" << std::endl;
    disp({ .x = 1, .z = 3 });

    return 0;
}
/*
output:
x = 1
y = 2
z = 0
---
x = 1
y = 0
z = 3
*/

引数として受け取るクラスを定義しておいて、そのクラスに対して指示付き初期化子で値を渡すようにしています。
本当は

auto
disp(struct { int x; int y; int z = 0; } pos) {
    // ...
}

みたいに引数で直接匿名クラスが定義できればいいんですが、こういう書き方はできないので仕方なくラムダのスコープ内でクラスを定義するようにしています。
あと指示付き初期化子の制限で『指示子の順序が宣言の順序と一致しない』とエラーになるので次のようにもかけません。

// Error: 指示子の順序が宣言の順序と一致しない
disp({ .y = 2, .x = 1 });

うーんキーワード引数で順序によってエラーになってしまうのはちょっと微妙ですねえ。
C++ で気持ちよくキーワード引数をかける日は来るのだろうか…。

参照

Vim 8.2 で Vim script に :const が追加された

Vim 8.2 で Vim script に :const が追加されました。
これは変数を定義する :let と同じように使用し、定義した変数を不変にします。
例えば

const x = 42

let x = 42
lockvar 1 x

と同等になります( lockvar1depth の指定 これにより const を使うことで不変となる変数を簡単に定義することができます。

" 以下と同等
" let x = 10
" lockvar 1 x
const x = 10

" Error: 再代入不可
" let x = 42

また、 :unlet:unlockvar を使うことで変数に再代入や再定義することができます。

const x = 10

" unlet や unlockvar すると再代入可能
unlet x
const x = 42

ちなみに暗黙的に :lockvardepth1 が設定されているので辞書の場合は要素に対して再代入可能です。

const data = { "x" : 10 }

" NG: 再代入不可
" const data = {}

" OK: 再代入可能
let data.x = 42

詳しくは :help lockvar を参照してください。
今まで :lockvar するのが手間だったので使ってなかったんですが、これからは :const を使うようにするとよさそうですね。

Vim 8.2 で Vim script にヒアドキュメントが追加された

Vim 8.2 で Vim script でヒアドキュメントがかけるになりました。
使い方は以下の通り

" END ~ END の間のテキストを行ごとのリストとして返す
let s:text =<< END
    if cond
        echo "homu"
    endif
END

echo s:text
" => ['    if cond', '        echo "homu"', '    endif']


" trim をつけると先頭のインデントを削除する
let s:text2 =<< trim END
    if cond
        echo "homu"
    endif
END

echo s:text2
" => ['if cond', '    echo "homu"', 'endif']

今までは無理やり文字列をリストで定義して…みたいなことをやっていたんですが、これだと複数行の文字列を定義しやすいですね。
ただし、文字列ではなくてリストを返す点に注意する必要があります。
なんで文字列じゃなくてリストを返すんだろうかこれ。