【一人 Ruby Advent Calendar 2017】ginza.rb で Ruby 2.5 の話を聞いてきた【21日目】

一人 Ruby Advent Calendar 2017 21日目の記事になります。
ちょっと話が前後してしまいますが、火曜日にginra.rb に初参加してきたのでそのまとめを。
今回は Ruby 2.5 のおさらいということで Ruby 2.5 の機能を参加者全員で眺めて生きながら意見を言っていくような内容でした。

当日読んでいた Ruby 2.5 に関する資料

以下、気になった機能を読んでいるときに出た意見などの適当なまとめ。
ちなみにわたしはまだ Ruby 2.5 の機能は殆ど触れてないです。

バックトレースおよびエラーメッセージが逆順に出力されるようになった

以下のようにバックトレースの順番が代わりました。

Ruby 2.4

$ ruby2.4 main.rb 
main.rb:3:in `map': undefined method `to' for 1:Integer (NoMethodError)
  from main.rb:3:in `homu'
    from main.rb:8:in `func'
  from main.rb:11:in `<main>'

Ruby 2.5

$ ruby main.rb
Traceback (most recent call last):
    3: from main.rb:11:in `<main>'
  2: from main.rb:8:in `func'
  1: from main.rb:3:in `homu'
main.rb:3:in `map': undefined method `to' for 1:Integer (NoMethodError)
  • 慣れの問題
  • リリースされたらみんな使うのでいろいろと意見が出てきそう
  • わたしはまだ確認してないのでなんとも言えない

トップレベルの定数検索が Object から削除された

以下の用にトップレベルで定義したクラスなどが X::Toplevel のように :: で参照できなくなった。

class Toplevel
end

class X
end

# この時にトップレベルの定数(クラスなど)が参照できなくなった
X::Toplevel
  • 今までの仕様は挙動としては正しいけど意図しているかは微妙だった
  • Ruby の挙動として見ると Object が検索されるのはわかる
  • Object 内で明示的に定義したクラスも参照されない
class Object
  class X
  end
end

class X2
end

# エラー
X2::X

yield_self

#tap と似ているがブロック内の戻り値を返す。

# yield_self だと break は不要
p [1, 2, 3].yield_self { |myself|
    myself.map { |it| it + myself.size }
}
# => [4, 5, 6]

Procの確保を遅延

  • 今まではブロック引数を明示化しているよりも『していないほうが』パフォーマンスがよかった
  • ブロック引数を明示化したほうがわかりやすいのでなるべくパフォーマンスに影響がでないように対応した
  • jruby だと逆にブロック引数を明示化したほうが早かった
  • パフォーマンスの問題はなくなったのでみんなブロック引数を明示化しよう
  • Ruby 2.5 は引数に &block を書いても速い!!! - onk.ninja

式展開の #to_s が refinements に対応

class X
end

using Module.new {
    refine X do
        def to_s
            "class X"
        end
    end
}

# refinements で定義された #to_s が呼ばれる
p "output: #{X.new}"
# => "output: class X"
  • 便利
  • ほかの内部で呼ばれるメソッドも refinements に対応してほしい
  • respond_to?to_proc も refinements に対応してほしい

Integer.sqrt

p Integer.sqrt(9)
#=> 3

p Integer.sqrt(15)
#=> 3

p Integer.sqrt(16)
#=> 4

String#casecmp で例外が発生しなくなった

Ruby 2.4

# Error no implicit conversion of Symbol into String (TypeError)
"homu".casecmp(:homu)

# Error no implicit conversion of Symbol into String (TypeError)
"homu".casecmp(1234)

Ruby 2.5

p "homu".casecmp(:homu)
# => nil
p "homu".casecmp(1234)
# => nil
  • TypeError のままでもよかったのでは?
  • Symbol だと nil を返していた
  • 一貫性のために TypeError から nil を返すように変更
    • と思ったけど Symbol でも TypeError だったのでうーん?
  • やっぱり TypeError のままの方がよかったのでは?

ERB#result_with_hash が追加

require "erb"

p ERB.new("Hello, <%=name%>").result_with_hash(name: "homu")
# => Hello, homu
  • #result_with_hash でコンテキストを渡した場合でも外部のコンテキストは参照できる
  • それならあまり意味が無いような気がする
  • コンテキストを明示的に上書きすることはできる

Set#===

case :apple
# Set のいずれかに含まれていれば
when Set[:potato, :carrot] then 'vegetable'
when Set[:apple, :banana]  then 'fruit'
end
#=> "fruit"
  • 集合と集合で比較するのではなくて #include? を使う
  • Set オブジェクト同士を比較したい?
  • #include? を呼び出すのは微妙かと思ったけどまあ Set ならいいかー
  • Method#=== を使用して s.method(:include?) みたいに代用することも可能

個人的に #===#include? にするのはなんかなーと思ってましたがまあ Set だからいいかーという位置に落ち着きました。
Range も同じですしね。
ただし、Array#=== を #include? のエイリアスにするのは絶対に許さん。

Ruby 2.5 に対する所感

そんな感じでざっくりまとめてみました。
上記以外にも Ruby 2.5 ではいろいろとメソッドや機能なんかが追加されたんですが割と『それはそうだよねー』みたいな機能とかが多かった印象です。

個人的によさげなのはやっぱりブロック引数周りのパフォーマンスの向上ですかね。
そもそもブロック引数を明示化してないのにブロック引数を渡すことができるという挙動があまり好きではないので、この機会に『ブロック引数を受け取る場合は必ずブロック引数を明示化する』ようになってほしいです。
あと Method#=== もそうですが Set#=== が入っていたのが知らなかったのでちょっと驚きました。
#=== ブーム来てるぞ…。

他には全員に影響があるって意味でバックトレース順が変わることですかねえ。
慣れの問題だったり、Ruby 2.5 の仕様のほうが見やすいって人もいると思いますが、やはり今までと表示形式が変わることに対する影響はコードの仕様が変わるぐらい大きいと思います。
この部分は Ruby 2.5 がリリースされてから色々と意見が出てきそうですねえ。
あ、あとそろそろ refinements さんはどこまでの範囲が影響するのかをしっかりとまとめてほしいところ。

参加してみて

そんな感じで初参加してきましたー。
参加する前はもくもく会みたいな感じで各々が Ruby 2.5 について調べるのかと思っていたのですが、実際は全員で機能を眺めて行くような感じでした。

会自体そこまで重い雰囲気ではなかったので割りと好き勝手意見を言うことができたり、他の人の意見なんかも聞くことができたのでとてもよかったです。
基本的にぼっちなのでこういうみんなで Ruby に対する意見を言い合うような機会は殆ど無いのでとても新鮮でした。
思っていることは割りとみんな同じなんだなーという風に共感できたのもよかったですね。

あと参加者の殆どは Ruby ユーザ(Ruby 本体の開発者ではない)なのでそういう目線で自由に意見を言っているのもいいなーと思いました。
結果的に Ruby 2.5 で追加される機能がなんとなく理解できたので参加してよかったです。
運営の方がありがとうございました。

【一人 vimrc advent calendar 2017】autocmd FileType 時に設定を追加する場合の注意点【20日目】

一人 vimrc advent calendar 2017 20日目の記事になります。
前回filetype の設定に関する記事を書いたのですが、今回はそれに関係するお話。

autocmd FileType 等で設定するときの注意点

autocmd FileType などで『任意の filetype に対して』設定を行う際は基本的には『そのバッファでのみ』反映される設定を行います。
ですので、オプションやキーマップなどはすべてバッファローカルにして設定する必要があります。

オプションの設定

オプションを設定する場合は :set ではなくて :setlocal を使用します。

" なんかいろいろ設定
setlocal cindent
setlocal cinoptions+=:0
setlocal matchpairs+=<:>

ただし、オプションによってはバッファローカルではなくてグローバルでしか設定できないオプションがあるので注意してください。

キーマッピング

キーマッピングは引数に <buffer> を追加することでバッファローカルとして設定することができます。

" 最後に定義された include 箇所へ移動してを挿入モードへ
nnoremap <buffer> <Space>ii :execute "?".&include<CR> :noh<CR> o

Ex コマンド

Ex コマンドの場合は -buffer を追加します。

command! -buffer Hoge echo "hoge"

autocmd

autocmd の場合はちょっと手間で、augroup を利用します。

" filetype 用の augroup を用意する
augroup my-vimfiler
   " その augroup の <buffer> を削除する
    autocmd! * <buffer>

   " autocmd は <buffer> を追加して定義する
   " こうすることでバッファローカルの autocmd になる
    autocmd CursorHold <buffer> execute "normal \<Plug>(vimfiler_print_filename)"
"     autocmd CursorMoved <buffer> execute "normal \<Plug>(vimfiler_print_filename)"
augroup END

このようにグローバルの autocmd と同様に autocmd が2回設定されることを防ぐために augroup を利用する必要があります。

まとめ

  • オプションなどを設定する場合は何に対して設定するのか注意しよう


ちなみに1つのバッファで複数の filetype を切り替えて使う場合の注意点もあるんですが、気力が尽きたので機会があれば別に書きます…。

【一人 Ruby Advent Calendar 2017】Shinjuku.rb #56 2017年開発厄落としLT大会でLTしてきた【20日目】

一人 Ruby Advent Calendar 2017 20日目の記事になります。
Shinjuku.rb で LT してきました。
今回も遅れてしまい申し訳なく。

case-when と === と gem

https://osyo-manga.github.io/slide-shinjukurb-56-case-when/#/

学生LTでも同じような事を話したのですが今回はもうちょっとマニアックな話をしてきました。
まあ何が言いたかっていうと Method#=== が入ったって言いたかっただけですね…。
と、いうかお酒を飲んだ勢いでライブコーディングしましたがぐだってしまって申し訳ねえ…。

ちなみに『なぜそんなに case-when ?』みたいな質問があったんですが、元々別の用途で === を使いたくていろいろと調べていたら『あれ、case-when もよくね?』と思った所存です。
実際、JavaScript のように厳密演算子みたいな用途ではなくて case-when 専用として #=== が定義されているので拡張性はとても高いと思います。
Ruby 2.5 で Set#=== も追加されたのでどんどん #=== 使っていきたい。

所感

今回はほぼ忘年会みたいな感じでお酒を呑みながらビザを食べて人をダメにするソファーに座りながら LT を聞くという素晴らしい会でした。
他の地域.rb と同様にとても話しやすい雰囲気でしたね。
お酒飲んでるとみんないい感じにいいたい放題言っていたのがとてもよかったです。
表題は今年の振り返りということでしたがわたしも割りと好き勝手話してきました。
また機会があれば、参加してみたいと思います。
運営の方々ありがとうございましたー。

【一人 vimrc advent calendar 2017】Vim から git commit する【19日目】

一人 vimrc advent calendar 2017 19日目の記事になります。
そろそろネタが尽きてきてそう。

Vim から git commit する

さて、Vim を使っているとその場でファイルを編集して、そのままファイルをコミットしたいと思うことが多いと思います。
そういう時に以下のようなコマンドを用意しておくと Vim からサクッと git commit する事ができるので便利です。

" :GitCommit {message}
command! -nargs=* GitCommit
\   echo system(printf("git commit %s -m %s", expand("%:p"), shellescape(<q-args>)))

こんな感じでよく使う外部コマンドも Vim のコマンドとしておくと便利です。
同様にコミットの取り消しもコマンド化しておくと便利です。

command! -nargs=* GitReset echo system("git reset --soft HEAD^")

便利。
こんな感じによく使うような外部コマンドや覚えるのがめんどくさい外部コマンドを Vim のコマンドとして定義しておくととても捗ります。

【一人 Ruby Advent Calendar 2017】トップレベルの変数の扱い【19日目】

一人 Ruby Advent Calendar 2017 19日目の記事になります。
前回、トップレベルのメソッドの扱いについて書いたのですが、今回は変数に関してです。

トップレベルのローカル変数

トップレベルで定義されたローカル変数は通常のローカル変数と同じような感じで使えます。

homu = "homu"

def func
    homu
end

# 関数内からは参照できない
# Error: undefined local variable or method `homu' for main:Object (NameError)
# p func

# 関数内で参照する場合はブロックを使ってキャプチャする
define_method(:func){
    homu
}
p func
# => "homu"

また、ローカル変数の寿命は『プログラム終了』までで、他のファイルからは参照できません。

トップレベルのインスタンス変数

さて、ややこしいのはトップレベルで定義されているインスタンス変数です。
トップレベルで定義されたインスタンス変数は self、つまり main オブジェクトのインスタンス変数として定義されます。

@homu = "homu"

p self.instance_variables
# => [:@homu]

p self.instance_variable_get :homu
# => "homu"

また、トップレベルのメソッド内等からも参照できるように『見えます』。

@homu = "homu"

def homu
    @homu
end

p homu
# => "homu"

これがちょっと勘違いするポイントなんですが、

わけではなくて

  • トップレベルで呼び出したメソッドのコンテキストが main オブジェクトになる

のでメソッド内で参照しているインスタンス変数が self、つまり main オブジェクトから参照する事になります。
まあ文章で説明してもわかりづらいと思うので実際にコードを書いてみましょう。

def homu
    self
end

# トップレベルで呼び出した場合、homu メソッドのコンテキストは main になる
p homu
# => main

class X
    def func
        homu
    end
end

# 別のオブジェクトのメソッド内で呼び出した場合は、そのオブジェクトのコンテキストになる
p X.new.func
# => #<X:0x00000000008796a0>

このようにトップレベルで定義されたメソッドは『呼びだされたレシーバのコンテキスト』として呼ばれます。
なので、トップレベルのメソッド内でトップレベルのインスタンス変数を参照しようとすると呼び出された場所によって結果が異なります。

@homu = "homu"

def homu
    @homu
end

# トップレベルで呼びだされた場合、コンテキストは main になるので
# main のインスタンス変数、つまりトップレベルの @homu が参照される
p homu
# => "homu"

class X
    def func
        homu
    end
end

# X のインスタンスメソッドから呼び出した場合は、コンテキストが X オブジェクトになるので
# X オブジェクトの @homu を呼びだそうとする
# X オブジェクトのインスタンス変数 @homu は未定義なので nil を返す
p X.new.func
# => nil

うーん、ややこしいですね…。

まとめ

  • トップレベルで定義したローカル変数はそのファイルのローカル変数として定義される
  • トップレベルで定義したインスタンス変数は main オブジェクトのインスタンス変数として定義される
  • トップレベルで定義されたメソッドのコンテキストはレシーバによって異なるので注意する


この挙動は Ruby

  • self がどのように参照されているのか
  • Object で定義されたメソッドはどのように扱われるのか

などを理解していればとても理にかなっている挙動にはなるんですが、そのあたりがわかっていないとかなり難しいところではありますね…。

【一人 Ruby Advent Calendar 2017】トップレベルメソッドの扱い【18日目】

一人 Ruby Advent Calendar 2017 18日目の記事になります。
Ruby でトップレベルに定義したメソッドがどういう扱いなのかイマイチわからなかったので調べてみた。

トップレベルの self

まず、トップレベルの self ですが、これは main という特別なオブジェクトになります。

p self
# => main

また、main という名前では参照することは出来ません。
トップレベルで定義したメソッドはこの main オプジェクトの private メソッドとして定義されます。

def homu
    "homu"
end

p self.private_methods(false)
# => [:include, :using, :public, :private, :define_method, :DelegateClass, :homu]

# Error: private method `homu' called for main:Object (NoMethodError)
# p self.homu

p self.send :homu
# => "homu"

main で定義されたメソッドは Objectインスタンスメソッドとしても定義される

ここが重要ぽいんですが、先ほどのようにトップレベルで定義したメソッドは『Objectprivateインスタンスメソッド』としても定義されます。

def homu
    "homu"
end

p Object.private_instance_methods(false)
# => [:DelegateClass, :homu]

Objectインスタンスメソッドとして定義されているので『Object を継承しているクラス(つまり殆どのオブジェクト)』からトップレベルのメソッドを呼び出すことが出来ます。

def homu
    "homu"
end

class X
    # Object#homu なのでどこからでも呼べる
    p homu
    #x=> "homu"

    def mado
        homu + homu
    end
end

p X.new.mado
# => "homuhomu"

# ただし、private メソッドなのでレシーバを付けた呼び出しは出来ない
# Error: private method `homu' called for #<X:0x0000000000ae12d0> (NoMethodError)
# p X.new.homu

ちなみに以下のように BasicObject を継承している場合は Object は継承していないので使用することは出来ません。

def homu
    "homu"
end

class X < BasicObject
    def mado
        # Error: undefined local variable or method `homu' for #<X:0x00000000021fd720> (NameError)
        homu + homu
    end
end

p X.new.mado

トップレベルで def homudef self.homu した場合の違い

余談ですが、トップレベルでメソッドを定義した場合と self に対して特異メソッドを定義した場合の追加です。

def self.singleton_method_added name
    return if name == :singleton_method_added
    p name
end

# self.singleton_method_added は呼ばれない
def homu
end

# private メソッドとして追加される
p Object.private_instance_methods(false)
p self.private_methods(false)


# self.singleton_method_added は呼ばれる
def self.mami
end

# Object には追加されない
p Object.instance_methods(false)

# self の public メソッドに定義される
p self.methods(false)

トップレベルでメソッドを定義した場合と self の特異メソッドとして定義した場合では挙動がかなり違います。

そもそもトップレベルのメソッドは main ではなくて Object に定義されるのでは…?

トップレベルで定義したメソッドは『main オプジェクトの private メソッド』として定義されると思っていたんですが、そもそも『main に定義される』のではなくて『Objectに定義される』という方が正しいのではないでしょうか。 これにより『Object にメソッドが定義されること』で結果的に『main でも使用することが出来る』という挙動の方が個人的には自然な気がします。
このあたりは Rubyソースコードを読んでみないとわかりませんが、認識としてはこっちの方がわかりやすい…。

まとめ

  • ×トップレベルのメソッドは mainprivate メソッドとして定義される
  • ○トップレベルのメソッドは Objectprivate メソッドとして定義される
    • なので結果的に main にもメソッドが追加される
  • トップレベルの self(main)とトップレベルで定義するメソッドは直接的な関係性はない(と思う)
  • Object クラスはほぼ全てのオブジェクトの継承リストに追加されている
    • なので Object クラスで定義されたメソッドはほぼ全てのオブジェクトから使える


Ruby のトップレベルの扱いむずかしい…。

【一人 vimrc advent calendar 2017】現在の filetype に対して簡単に設定を追記する【18日目】

一人 vimrc advent calendar 2017 18日目の記事になります。

filetype に対する設定

さて、vimrc で任意の filetype に対して設定を行う場合、以下のように autocmd を使用することが多いです。

" filetype=cpp に対する設定
autocmd Filetype cpp setlocal matchpairs+=<:>

上記のような設定でもいいんのですが vimrc の量が増えてくると管理しづらくなったり、新しく設定を追記するのもちょっと手間になってきます。
そこで、次のようなスクリプトを用いて vimrc ではなくて『Vim プラグインとして』扱うと管理しやすくなります。

Vim プラグインとして filetype 用のファイルを管理する

まず、保存するディレクトリを決めます。

" このパスに設定を保存するようにする
let $VIMFILETYPE_PLUGIN = "~/.vim/filetype"

次にこのディレクトリが Vim から読み込まれるように 'runtimepath' へと追記します。

set runtimepath+=$VIMFILETYPE_PLUGIN

このように 'runtimepath' に追加することで filetype=hoge を行った際に自動的に #VIMFILETYPE_PLUGIN/ftplugin/hoge.vim などファイルが読み込まれるようになります。
最後に以下のような編集用のスクリプト用いて filetype に対する設定ファイルを簡単に開きます。

command! EditFileType execute "split " . $VIMFILETYPE_PLUGIN . "/after/ftplugin/" .&filetype. ".vim"
nnoremap <silent> <Space><CR> :<C-u>EditFileType

あとは :EditFileType などでスクリプトファイルを開いてそこに設定を記述していけば、:set filetype= を行った際にその設定が読み込まれるようになります。
キーマッピング<Space><CR>)や開き方(split)などはお好みで変えてください。
また、注意点としては、

  • 設定を記述しただけでは反映されない
    • 再度 :set filetype={filetype} を行うことで反映される
  • グローバル(:set)ではなくてローカル(:setlocal)で設定を記述する

あたりですかね。
ちなみにランタイムのファイルよりも後に読み込まれるように $VIMFILETYPE_PLUGIN/ftplugin/ ではなくて $VIMFILETYPE_PLUGIN/after/ftplugin/ にファイルを保存しています。
こうすることで既存の設定を上書きすることが出来ます。

まとめ

  • filetype ごとの設定は autocmd FileType を使うのが簡単
  • vimrc で管理しづらくなってきたらプラグインとして管理するのも手
    わたしは割と初期の頃からこの設定を使っているんですがかなり便利です。