2019年 まとめ

今年一年の振り返りです

Ruby

  • Ruby にパッチ投げたりチケット立てたりした
  • 知り合いのコネで開発者会議に参加させてもらった
    • いつも内容が濃ゆい
  • 勉強会で Ruby の LT しまくった
    • 詳細は後述参照
  • reline に GitHub Actions 追加したりとか少し貢献できた
  • gem はあんまりつくれなかった
  • 勉強会の懇親会で Ruby にいろいろと議論する事ができた
  • ActiveRecord の実装読みまくって ActiveRecord 力が上がった気がする

勉強会

買ってよかったもの

買ってよかったマンガ

反省点

  • おもしろ gem がつくれなかった
  • 全然アウトプットできなかった
  • 6月ぐらいから kindle でマンガ買うのにハマって気づいたら 600冊以上買ってた
  • 全体的に進捗がない

2020年の目標

  • Ruby にもっと貢献したい
    • Refinements 周りのバグ修正した
  • gem もっと作るぞ!
  • 勉強会でもっと LT したい
  • Ruby 以外の勉強会にも行きたい
  • Vim 8.2 もリリースされたのでモダンな開発環境つくっていきたい
  • もっと雑にブログを書いていきたい
  • もっと雑に bugs.ruby にコメントしていきたい
  • Ruby 以外にも手を出していきたい

ではでは、よいお年を〜。

つよつよパソコンを新しく組み立てた

f:id:osyo-manga:20191230223937j:plain

勢いでつよつよパソコンを新しく組み立てました。
構成は以下の通り。

構成

※ 2019/12/30 現在 http://niku.webcrow.jp/?MwkNTdQxyUpN0TGJSDXWMQkzytYx8XZ30dExCc111AFSbt5A4YyMbB09Qx0YBCEwQw8IyowA

AMD Ryzen 9 3900X BOX

もともとは 3900X が77000円ぐらいしててそれなら +2万出して 3950X 買ったほうがいいなーと思っていたんですが、3950X が売り切れ + 3950X が発売した影響で 3900X が6万ちょいぐらいまで値下がっていたので 3900X にしました、 Intel CPU と比べて AMD CPU はコスパよくていいですね!!

Corsair H150i PRO RGB CW-9060031-WW

NH-D15S と迷ったんですが 3900X ならやっぱり簡易水冷がいいかな〜〜〜と思って初めて簡易水冷にしました。
あとクソデカヒートシンクだとメモリとかが指しづらいので空間的余裕がある簡易水冷の方がよさそうだな〜〜〜思ってこっちにしました。
実際ケースに取り付けるのは大変だったんですが、つけた後は空間的余裕ができてかなり使い勝手がよくなりました。
あとサイバーマンデーで安くなっていたので勢いでポチった。

G.Skill F4-3600C16D-32GTZNC [DDR4 PC4-28800 16GB 2枚組]

メモリは適当に 16GB x 2 の安いやつを買おうと思っていたんですが、ここの記事とかを参考にして Ryzen に適しているらしい F4-3600C16D-32GTZNC にしました。
無駄にめっちゃ光る

ASRock X570 Steel Legend

高耐久性でコスパがいいってことでこのマザボにしました。
概ね問題なかったんですが、M.2 を取り外す時にヒートシンクをいちいち外さないとダメなのがちょっと手間でした。

MSI Radeon RX 580 ARMOR 8G OC [PCIExp 8GB]

唯一前のパソコンから引き継いだパーツです。
特にゲームなどしないので 2万ぐらいでコスパのいいグラボを付けてます。

CFD PG3VNF CSSD-M2B5GPG3VNF

第3世代Ryzen ならやっぱり PCI-Express4.0 に対応している NVMe M.2 でしょ!ってことでこれにしました。
カタログスペックだけでも

  • 読込速度 : 5000 MB/s
  • 書込速度 : 2500 MB/s

とチョッパヤです。
500GB でも1万ちょいなのでかなりコスパがいいです。
本当は 1TB がほしかったんですが売り切れてて 500GB にしました

ANTEC P101 Silent

360mm の簡易水冷が設置できるケースを選びました。
その結果かなり大きなケースになってしまいなんと 3.5インチベイが 8個も置けます。

ANTEC NeoECO Gold NE750G

電源はまあなんか適当に選びました。

所感

前回パソコンを新しくしたのは 3年前でした。
前回は Core2DuoCore i7 6700K へステップアップだったので劇的に早くなったことを体感したんですが、今回はそこまで感じなかったっていうのが正直なところですね。
いや、スペック的には Core i7 6700K → Ryzen 9 3900X でも 3倍ぐらいスコアは上がっているはずなんですけどね。
あとケースが原因かもしれないんですが簡易水冷の取り付けにめっちゃ苦労しました。
今回買ったケースだと一旦 3.5インチベイを全部取り外さないとダメだったりケースについてるファンも取り替えないとダメでいろいろと手間でした。
その分、付けてしまえばケースの中はかなりすっきりしたんですが。
あとめっちゃ光ります。

ひとまずこれで10年ぐらい戦えるぞ!!
ちなみに諸々移行作業が終わってからグラボのドライバをアップデートしたらログインマネージャが起動しなくなり OS から入れ直しました

RSpec の satisfy マッチャを便利に使う

Model のテストなどを書く時に次のように任意のリレーションに対して意図するクエリが追加されているかどうかをテストすることがあると思います。

class User < ActiveRecord::Base
  # rate 基準で上位10人を絞り込む
  scope :top10, -> { where(active: true).order(:rate).limit(10) }
end

RSpec.describe User do
  describe ".top10" do
    # scope top10 に対して意図するクエリが追加されていることをテストする
    subject { User.top10.to_sql }
    it { expect(subject.scan('WHERE "users"."active" = TRUE').one?).to be_truthy }
    it { expect(subject.scan('ORDER BY "users"."rate" ASC').one?).to be_truthy }
    it { expect(subject.scan('ASC LIMIT 10').one?).to be_truthy }
  end
end

テストとしては別に問題ないと思うんですが、 expect に負担がかかり過ぎてますね。
これを expect に負担をかけるのではなくてマッチャでがんばって書きたいと思います。

satisfy マッチャを使う

RSpec には satisfy マッチャがあります。
これは expect に渡した値を引数としたブロックを渡し、テストが成功しているか失敗しているかを判定することができるようになります。
例えば、次のように利用することができます。

describe "test" do
  # satisfy に渡したブロックが真を返せばテストがパスする
  # x = 1
  it { expect(1).to satisfy { |x| x.odd? } }
  # x = 2
  it { expect(2).to satisfy { |x| x.even? } }
end

先程の Model のテストは satisfy を利用すると次のように書くことができます。

RSpec.describe User do
  describe ".top10" do
    subject { User.top10.to_sql }
    # is_expected で書くことができる!
    it { is_expected.to satisfy { |sql| sql.scan('WHERE "users"."active" = TRUE').one? } }
    it { is_expected.to satisfy { |sql| sql.scan('ORDER BY "users"."rate" ASC').one? } }
    it { is_expected.to satisfy { |sql| sql.scan('ASC LIMIT 10').one? } }
  end
end

更にヘルパメソッドを定義することでオレオレマッチャみたいなのをさくっと定義することもできます。

RSpec.describe User do
  describe ".top10" do
    # let っぽく記述
    define_method(:scan_once) { |query| satisfy { |sql| sql.scan(query).one? } }
    subject { User.top10.to_sql }
    # 簡略化してかける
    it { is_expected.to scan_once 'WHERE "users"."active" = TRUE' }
    it { is_expected.to scan_once 'ORDER BY "users"."rate" ASC' }
    it { is_expected.to scan_once 'ASC LIMIT 10' }
  end
end

これは便利。

Ruby の remove_method と undef_method の違い

さてさて、せっかくアドベントカレンダーでブログを書き続けているのでどうせならこのまま書き続けていこうかなーと思います。
まあすぐに止まりそうですが。
今回の内容は某所でちょっと話題になっていたので覚書。

remove_method と undef_method の違い

remove_method はメソッドを『削除』にし、undef_method はメソッドを『未定義』します。
どういうことかというと継承リストに呼び出せるメソッドが複数ある場合に違いがあります。

class Base
  def hoge
    "Base#hoge"
  end
end

class Super < Base
  def hoge
    "Super#hoge"
  end
end

# Super クラスで定義したメソッドが呼ばれる
pp Super.new.hoge
# => "Super#hoge"


class Super
  # Super で定義されているメソッドを削除する
  remove_method :hoge
end

# Base クラスで定義したメソッドが呼ばれる
pp Super.new.hoge
# => "Base#hoge"


class Super
  # Super で定義されているメソッドを未定義
  undef_method :hoge
end


# Super クラスからは undef_method したメソッドが呼べなくなる
# error: undefined method `hoge' for #<Super:0x00005631f4397278> (NoMethodError)
# pp Super.new.hoge

# こういう書き方だと呼び出すことは可能
pp Base.instance_method(:hoge).bind(Super.new).call

remove_method だとまさに『そのクラスのメソッドを削除する』っていう動作になるんですが、 undef_method だと『メソッドを呼び出せなくする』っていう動作になります。
たまーーーーーーに一時的にメソッドを生やしてその後削除することがあるんですが remove_methodundef_method の違いがわかってないと思わぬところで嵌りそうですねえ。

Ruby 2.7 がリリース

例年どおり昨日 25日に Ruby 2.7 がリリースされました。

Ruby 2.7 の開発はだいぶ追っていたんですが追加された機能もあれば実装されたけど最終的に Revert されてしまった機能もあり割と起伏が激しい印象です。
なんだよ、最終的に Revert されたじゃん!!と気持ちが強かったんですが、実際リリースされるとやっぱり便利機能が多く追加されたので便利。
irb めっちゃ強くなったとかパターンマッチが(実験的に)実装されたとかキーワード引数めっちゃ警告出過ぎワロスみたいなのがあるんですが、地味にわたしが投げたパッチも取り込まれているのでいくつか紹介してみたいと思います。

Time#floor / Time#ceil

Time にはもともとミリ秒を丸める Time#round があったんですが、切り捨てと切り上げがなかったので追加しました。

time = Time.now

pp time            # => 2019-12-26 22:14:44.389964779 +0900
# 丸め込み
pp time.round(6)   # => 2019-12-26 22:14:44.389965 +0900
# 切り捨て
pp time.floor(6)   # => 2019-12-26 22:14:44.389964 +0900
# 切り上げ
pp time.ceil(6)    # => 2019-12-26 22:14:44.389965 +0900

自分で追加した程度にはめっちゃほしかったので Ruby 2.7 使いたい。

Refinements が method / instance_method に対応

Refinements の制限を緩和していこうキャンペーンで methodinstance_method でも Refinements で定義したメソッドを参照できるようにしました。

using Module.new {
  refine String do
    def twice
      self + self
    end
  end
}

# 2.7 では method や instance_method で Refinements で定義したメソッドが取得できるようになった
p "homu".method(:twice).call
p String.instance_method(:twice).bind("homu").call

便利。

その他個人的な注目機能

Ruby 2.7 ではいろいろな魅力的な機能が追加されているんですがその中でも個人的に「お、これめっちゃええやん!!」みたいなのをいくつか紹介してみます。

Time#inspect がミリ秒まで含まれるようになった

Time#inspect がミリ秒まで含まれるようになりました。

require "time"

time = Time.now

# 今まではミリ秒は切り捨てられていた
pp time
# => 2019-12-26 22:22:12 +0900

# ミリ秒まで表示する場合は iso8601 など使う必要があった
pp time.iso8601(10)
# => 2019-12-26 22:22:12.660985941 +0900

# Ruby 2.7 ではミリ秒まで表示されるようになった
pp time
# => 2019-12-26 22:22:12.660985941 +0900

一日に iso8601(10) って10000万回ぐらい書いているのでこれはありがたい…。

Method#inspectシグネチャと定義場所が含まれるようにあった

Method#inspectシグネチャと定義場所が含まれるようになりました。

def plus(a, b)
  a + b
end

# Ruby 2.6
p method(:plus)
# => #<Method: main.plus>

# RUby 2.7
p method(:plus)
# => <Method: main.plus(a, b) /tmp/test.rb/69:1>

ActiveRecord とか読む時にメソッドの定義場所を調べることは多かったんですが、定義場所の情報を取得する場合 Method#source_location を使う必要がありました。
source_location も今年100000億回ぐらい書いたのでもう書かなくていいのは便利ですね。

警告の制御

Ruby 2.7 ではキーワード引数で警告が出たり、新しい機能であるパターンマッチを使うと experimental であると警告が出るようになります。
この警告の出力を制御するために新しいオプションが追加されました。
例えば -W:no-deprecated を使用すればキーワード引数などの deprecated な警告が出力されなくなり、 -W:no-experimental を使用すればパターンマッチを使用した場合の警告などが出力されなくなります。
Ruby 2.7 を使いたいが外部ライブラリで警告が出てつらい、みたいな場合はこのオプションを利用してみるとよいです。

これからは

され、Ruby 2.7 が無事にリリースされたということはこれから Ruby 3.0 の開発が始まります。
Ruby 3.0 でも何かしら開発に貢献できていければいいかなーと思います。
とりあえず Refinements 周りのバグを見直していきたいですねえ…。

参照

【一人 C++20 Advent Calendar 2019】C++ にコンセプトがやってくる!【25日目】

一人 C++20 Advent Calendar 2019 25日目の記事になります。

C++ にコンセプトがやってくる!

ついに C++ にコンセプトがやってきました! C++11 の時代から長かった…。
コンセプトとはテンプレートパラメータに対して任意の制約を指定することができるようになる機能になります。
細かいところを話し出すと止まらないので簡単な例を記述すると以下のような使い方になります。

#include <string>
#include <iostream>

// T 型に対する制約を定義する
template<typename T>
concept printable = requires (T& t) {
    // T に対して任意のメンバ関数が呼べるかどうかの条件
    t.name();

    // 複数書くことで複数の制約を定義できる
    // また、以下のように書くことでメソッドの戻り値に対する制約も定義できる
    { t.sound() } -> std::string;
};

// テンプレート引数を定義する時に typename や class の代わりに
// concept で定義した制約名を記述できる
// この場合は printable の条件の型のみをテンプレートで受け取ることになる
template<printable T>
void
print(T const& obj) {
    std::cout << obj.name() << " : " << obj.sound() << std::endl;
}

// こちらは通常の関数テンプレートになる
// printable の制約に当てはまらなかった場合にこちらを呼び出す
template<typename T>
void
print(T const& obj) {
    std::cout << "出力できません" << std::endl;
}


struct cat {
    std::string name() const { return "ねこ"; }
    std::string sound() const { return "にゃーん"; }
};

struct dog {
    std::string name() const { return "いぬ"; }
    std::string sound() const { return "わーん"; }
};

struct X {
    int name() const { return 42; }
    int sound() const { return 42; }
};

int
main(){
    // OK: printable の条件に当てはまってる
    print(cat{});
    print(dog{});

    // NG: printable の条件に当てはまらない
    print(X{});

    return 0;
}
/*
output:
ねこ : にゃーん
いぬ : わーん
出力できません
*/

C++メタプログラミングをやったことがある人なら必ず書いたことがあると思うんですが SFINAE を使ったテンプレート型の条件分岐ってめちゃくちゃむずかしいんですよね。
しかし、それがコンセプトを使うことで上のコードのようにめちゃくちゃすっきりと記述することができます。
そうそう、これだよこれ、これでいいんだよ。
もちろん上の使い方以外にも様々な用途でコンセプトを利用することができます。
詳しくは以下のまとめを読んでみてください。
いやー C++11 から長かったんですがやっとコンセプトが入りましたねえ…。
また、言語機能として追加されるだけではなくて標準ライブラリにもいくつか汎用的な制約が定義されています。
C++20 が楽しみですねー。

アドベントカレンダーを終えて

と、言うことで今日がクリスマス!そしてアドベントカレンダーが終わり!!! 勢いで始めたアドベントカレンダーだったんですがなんとか無事に完走することができました。
一つ一つの内容はとても薄っぺらいですが
始める前は C++20 って何が追加されるんじゃ???って感じだったんですが実際にどういう機能が追加されるのかを調べてみると細かい追加や修正がかなりされていることがわかりました。
特に「C++ で絶対に constexpr するぞ!!!」という気持ちはめっちゃ伝わってきました。
まだ各コンパイラでは追加されてない機能やライブラリがあるので「ホンマに来年使えるようになるの???」みたいな気持ちがあるんですが、各コンパイラが対応されたらまた触ってみたいですねー。
それでは皆さん良いお年を。

参照