Ruby の Warning[:experimental] = false は実行時に反映されないケースがある
某所でそういう話が出ていたのでいろいろと調べてまとめてみました。
Warning[:deprecated] = true / false
で実行時に非推奨な警告を制御できる
Warning[:deprecated]
に true / false
を割り当てることで実行時に非推奨な警告の出力を制御することができます。
例えば Dir.exists?
は Ruby 3.0 以降では非推奨なので Warning[:deprecated]
が真だと警告文が出力されます。
ちなみに Ruby 2.7 系で試す場合は Enumerator.new([], :each)
など使うと警告が出ます。
# Ruby 2.7.2 以降だとデフォルトでは false pp Warning[:deprecated] # => false # no wanirng Dir.exists?("") # これに true を設定するとそれ以降に呼ばれたメソッドなどで警告が出るようになる Warning[:deprecated] = true # warning: Dir.exists? is deprecated; use Dir.exist? instead Dir.exists?("")
こんな感じで実行時に警告文の出力を制御することができます。
Warning[:experimental] = true / false
の場合は?
Ruby 3.0 ではいくつかの機能が実験的に導入されています。
例えば Ractor
は Ruby 3.0 時点ではまだ実験的な機能になります。
さて、このような実験的な機能を使用すると警告文が出力されることがあります。
# warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues. Ractor.new {}
この警告文の出力ですが Warning[:experimental]
を用いることで『実験的な機能に対する』警告文の出力を制御することができます。
# デフォルトでは true pp Warning[:experimental] # => true # 警告が出る # warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues. Ractor.new {} # 警告の出力を無効にする Warning[:experimental] = false # 警告が出ない # no warning Ractor.new {}
このように Warning[:experimental] = false
にする事で警告文が出力されなくなります。
Warning[:experimental] = false
で制御できないケースもある
Ruby 3.0 ではパターンマッチの in
を 1行でかける構文も実装されました。
これも Ractor
同様にまだ実験的な機能なので使用すると警告文が出ます。
user = { name: "homu", age: 14 } # warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby! if user in { age: 1..20 } # ... end
ではこの警告文を Warning[:experimental]
で制御することができるのでしょうか?結論から言うとできませんでした。
user = { name: "homu", age: 14 } # 実験的な警告を無効にする Warning[:experimental] = false # 実験的な警告を無効にしているのに警告が出てしまう # warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby! if user in { age: 1..20 } # ... end
これはなぜかというと in
を使ったときの警告文の出力が『コンパイル時』に制御されているためです。
なので次のように Ractor.new
が実行時に呼ばれないと警告文を出さないのに対して、 in
の場合は実行時に呼ばれていなくても警告文がでます。
def foo # これは実行時に foo メソッドが呼ばれていないので警告はでない Ractor.new end def hoge # これは実行時に hoge メソッドが呼ばれていなくても警告が出る 42 in n end
一方で Warning[:experimental] = false
というコードは『実行時』に制御を行っているため、コンパイル時には反映されません。
なので例えば eval
などを使って実行時に Ruby のコードを実行する場合は Warning[:experimental]
の設定が反映されます。
user = { name: "homu", age: 14 } Warning[:experimental] = true # warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby! eval <<~EOS if user in { age: 1..20 } # ... end EOS Warning[:experimental] = false # no warning eval <<~EOS if user in { age: 1..20 } # ... end EOS
このように Warning[:experimental]
で警告文の出力を制御する場合は反映される機能とそうでない機能があるので注意する必要があります。
実験的な機能の警告を無効にするには?
Ruby のコマンドオプションに -W:no-experimental
を付けることで警告文を無効にすることができます。
$ cat main.rb user = { name: "homu", age: 14 } if user in { age: 1..20 } # ... end # 警告が出る $ ruby main.rb main.rb:3: warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby # -W:no-experimental を付けると警告がでない $ ruby -W:no-experimental main.rb $
実験的な機能の警告文を無効にしたい場合はコマンドオプションで制御する方がよさそうですね。
おれの考えた最強の denite.nvim の設定
年明けぐらいからちょこちょこ denite.nvim を使い始めて設定を追加したりプラグイン側でいろいろと対応してもらったりしていたのですがだいぶいい感じになってきたので一旦現状の設定を書き溜めておきます。
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " denite のバッファの設定 augroup my_denite autocmd! autocmd FileType denite call s:denite_my_settings() autocmd FileType denite-filter call s:denite_filter_my_settings() augroup END """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " denite.nvim のバッファの設定 function! s:denite_my_settings() nnoremap <silent><buffer><expr> <CR> \ denite#do_map('do_action') nnoremap <silent><buffer><expr> d \ denite#do_map('do_action', 'delete') nnoremap <silent><buffer><expr> p \ denite#do_map('do_action', 'preview') nnoremap <silent><buffer><expr> q \ denite#do_map('quit') nnoremap <silent><buffer><expr> i \ denite#do_map('open_filter_buffer') nnoremap <silent><buffer><expr> <Space> \ denite#do_map('toggle_select').'j' nnoremap <silent><buffer><expr> <C-Space> \ denite#do_map('toggle_select').'j' nnoremap <silent><buffer><expr><nowait> t \ denite#do_map('do_action', 'tabswitch') nnoremap <silent><buffer><expr> a \ denite#do_map('choose_action') nnoremap <silent><buffer><expr> <C-g> \ denite#do_map('echo') endfunction """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " denite.nvim のフィルタバッファの設定 function! s:denite_filter_my_settings() abort augroup ftplugin-my-denite autocmd! * <buffer> " denite-filter 用のキーマッピング " NOTE: このタイミングじゃないとキーマッピングが反映されない " フィルタリング中に Enter を押すと選択されている候補のデフォルトアクションを実行する autocmd InsertEnter <buffer> imap <silent><buffer> <CR> <ESC><CR><CR> " インサートを抜けた時に自動的にフィルタウィンドウを閉じる autocmd InsertEnter <buffer> inoremap <silent><buffer> <Esc> <Esc><C-w><C-q>:<C-u>call denite#move_to_parent()<CR> augroup END " フィルタバッファでは自動補完を無効にしておく call deoplete#custom#buffer_option('auto_complete', v:false) " ステータスラインに file/rec(10/100) のような候補数を表示させる setlocal statusline=%!denite#get_status('sources') " カーソルキーで候補の選択を移動させる inoremap <silent><buffer> <Down> <Esc> \:call denite#move_to_parent()<CR> \:call cursor(line('.')+1,0)<CR> \:call denite#move_to_filter()<CR>A inoremap <silent><buffer> <Up> <Esc> \:call denite#move_to_parent()<CR> \:call cursor(line('.')-1,0)<CR> \:call denite#move_to_filter()<CR>A " 同様のことを <C-j><C-k> で inoremap <silent><buffer> <C-j> <Esc> \:call denite#move_to_parent()<CR> \:call cursor(line('.')+1,0)<CR> \:call denite#move_to_filter()<CR>A inoremap <silent><buffer> <C-k> <Esc> \:call denite#move_to_parent()<CR> \:call cursor(line('.')-1,0)<CR> \:call denite#move_to_filter()<CR>A " 同様のことを <C-j><C-k> で inoremap <silent><buffer> <C-n> <Esc> \:call denite#move_to_parent()<CR> \:call cursor(line('.')+1,0)<CR> \:call denite#move_to_filter()<CR>A inoremap <silent><buffer> <C-p> <Esc> \:call denite#move_to_parent()<CR> \:call cursor(line('.')-1,0)<CR> \:call denite#move_to_filter()<CR>A return " インサートを抜けた時に自動的に候補のバッファに移動する imap <silent><buffer> <Esc> <Esc>:call denite#move_to_parent()<CR> imap <silent><buffer> <C-[> <C-[>:call denite#move_to_parent()<CR> " フィルタバッファで <CR> すると候補を実行する inoremap <silent><buffer> <CR> <Esc> \:call denite#move_to_parent()<CR> \<CR> " 別キーで実装 inoremap <silent><buffer> <C-CR> <Esc> \:call denite#move_to_parent()<CR> \<CR> inoremap <silent><buffer> <C-m> <Esc> \:call denite#move_to_parent()<CR> \<CR> endfunction """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " :Denite のデフォルトの設定 let s:denite_default_options = {} " 絞り込んだワードをハイライトする call extend(s:denite_default_options, { \ 'highlight_matched_char': 'None', \ 'highlight_matched_range': 'Search', \ 'match_highlight': v:true, \}) " denite を上に持っていく call extend(s:denite_default_options, { \ 'direction': "top", \ 'filter_split_direction': "top", \}) " フィルタのプロンプトを設定 call extend(s:denite_default_options, { \ 'prompt': '> ', \}) " 大文字小文字を区別してフィルタする call extend(s:denite_default_options, { \ 'smartcase': v:true, \}) " ステータスラインに入力を表示しないようにする " call extend(s:denite_default_options, { "\ 'statusline': v:true, "\}) " デフォルトで絞り込みウィンドウを開く " call extend(s:denite_default_options, { " \ 'start_filter': v:true, " \}) call denite#custom#option('default', s:denite_default_options) """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " kind の設定 " ファイルを開く際のデフォルトアクションを tabswitch にする call denite#custom#kind('file', 'default_action', 'tabswitch') """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " file の設定 " call denite#custom#source('file', 'matchers', ['matcher/regexp']) if &rtp =~ "devicons" call denite#custom#source('file', 'converters', ['devicons_denite_converter', 'converter/abbr_word']) else call denite#custom#source('file', 'converters', ['converter/abbr_word']) endif """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " file/rec の設定 call denite#custom#source("file/rec", "max_candidates", 100) if &rtp =~ "devicons" call denite#custom#source('file/rec', 'converters', ['devicons_denite_converter', 'converter/abbr_word']) else call denite#custom#source('file/rec', 'converters', ['converter/abbr_word']) endif " プロジェクト直下のファイル一覧を表示する + 新規ファイル作成 nnoremap <Space>uff :DeniteProjectDir file/rec<CR> """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " quickrun_config の設定 " denite-quickrun_config の並び順を単語順にする call denite#custom#source('quickrun_config', 'sorters', ['sorter/word']) """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " grep の設定 " ripgrep で grep if executable("rg") call denite#custom#var('file/rec', 'command', \ ['rg', '--files', '--glob', '!.git', '--color', 'never']) call denite#custom#var('grep', { \ 'command': ['rg'], \ 'default_opts': ['-i', '--vimgrep', '--no-heading'], \ 'recursive_opts': [], \ 'pattern_opt': ['--regexp'], \ 'separator': ['--'], \ 'final_opts': [], \ }) endif call denite#custom#source("grep", "max_candidates", 300) " ファイル名を含めて絞り込めるようにする converter/abbr_word を追加する " devicons_denite_converter を使うと絞り込みがおかしくなるので一旦無効にする " call denite#custom#source('grep', 'converters', ['converter/devicons_denite_converter', 'converter/abbr_word']) call denite#custom#source('grep', 'converters', ['converter/abbr_word']) nnoremap <Space>gr :DeniteProjectDir grep<CR> """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " gitto の設定 command! GitBranch Denite gitto/branch
denite-grep 周りでプラグイン間の競合があり、かなり設定に手間取っていたんですがだいぶ理想の挙動になりました。
設定を覚えるのは難しいですが、設定すればだいたいのことを実現することができるので便利ですねえ。
2021/05/06 今週の気になった bugs.ruby のチケット
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。
[Bug #17838] Set#intersect?
and enumerables
- 現状だと
Set#intersect?
にはSet
しか渡せないんですがそれをEnumerable
を渡せるようにしようという提案っぽい
[1, 2, 3].intersect?(Set[2, 3, 4]) # => true Set[2, 3, 4].intersection([1, 2, 3]) # => Set[2, 3] # これを渡せるようにしたいぽい? Set[2, 3, 4].intersect?([1, 2, 3]) # => ArgumentError
- 最近
Array#intersect?
が追加されたけどSet#intersect?
は前からあったんですね
2021/04/29 今週の気になった bugs.ruby のチケット
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。
[Feature #17830] Add Integer#previous and Integer#prev
Integer#next
の逆のInteger#previous
を追加する提案- 現状は
Integer#next
のエイリアスとしてInteger#succ
があり、それの逆のInteger#pred
がある Integer#succ
<->Integer#pred
はわかりやすいが、Integer#next
<->Integer#pred
は分かりづらいのでInteger#next
<->Integer#previous
を追加しよう、というモチベーションらしいInteger#pred
自体使ったことなかったけど実際どういうケースで使うんですかね?- コメントには
(number - 1).times { ... })
をnumber.pred.times { ... })
みたいに書くとはかかれていた
[Feature #17837] Add support for Regexp timeouts
- 次のような正規表現マッチを行った場合にめちゃくちゃ時間がかかってしまうという問題がある
- ReDoS 攻撃という脆弱性につながるらしい
- その正規表現の書き方で大丈夫? ReDoS 攻撃の怖さと対策方法 | yamory Blog
# この処理はいつまで経っても終わらない… /A(B|C+)+D/ =~ "A" + "C" * 100 + "X"
2021/04/22 今週の気になった bugs.ruby のチケット
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。
[Bug #9542] Delegator does not delegate protected methods
- 以下のコードが Ruby 2.0 と 2.1 で差異がある、というバグ報告
require "delegate" class Cow def unprotected_moo "mooooo!" end protected def protected_moo "guarded mooooo!" end end my_cow = Cow.new puts SimpleDelegator.new(my_cow).unprotected_moo # => "mooooo!" # protected_moo は protected なのに呼び出すことができる puts SimpleDelegator.new(my_cow).protected_moo # 2.0 => "guarded mooooo!" # 2.1 以降 => undefined method `protected_moo' (NoMethodError) puts my_cow.unprotected_moo # => "mooooo!" puts my_cow.protected_moo # 2.0 => "guarded mooooo!" # 2.1 以降 => protected method `protected_moo' called (NoMethodError)
- これ自体は期待する挙動なので Reject されている
- そもそもチケット自体が7年前のやつだった
[Bug #17814] inconsistent Array.zip behavior
- 以下のように
Array#zip
だとイテレーションが1回余計に呼ばれているというバグ報告
i = 0 # 1 ずつ増えるカウンタ e = Enumerator.produce { i += 1 } # 1つ余計にイテレーションが発生する p [0, 0, 0, 0].zip e # => [[0, 1], [0, 2], [0, 3], [0, 4]] p i # => 5 # Enumerable#zip だと再現しない p [0, 0, 0, 0].each.zip e # => [[0, 6], [0, 7], [0, 8], [0, 9]] p i # => 9
Enumerable#zip
だと問題ないので対応する場合はこっちを使うとよさそう- これは最新版では修正済み
[Feature #17785] Allow named parameters to be keywords
- 先週も書いてた
キーワード_
という名前で変数参照できるようにしよう、という提案
def check(arg, class:) # _ を付けて参照できるようにする arg.is_a?(class_) end check(42, class: Integer) # => true
__params__
みたいな値で受け取れるようにしようという提案があってこれは普通にほしいと思った
def check(arg, class:) arg.is_a?(__params__[:class]) end check(42, class: Integer) # => true
- ただし、キーワード引数以外の引数をどうするかの課題がある
__params__
というよりかは__keyword_params__
みたいな?
[Feature #17718] a method paramaters object that can be pattern matched against
- 次のようにキーワード引数を一括で返すメソッドを追加する提案
- パターンマッチで利用することを想定している
def get_perdiem(city: nil, state: nil, zip:nil) case parameters_match # (return an object of the parameters we can pattern match on) in {zip: zip} find_perdiem_by_zip(zip) in {state: s, city: c} find_perdiem_by_state_and_city(s, c) in { state: s} find_perdiem_by_state(s) else raise 'need combination of zip, city,state' end end
- これは普通にやりたいユースケースだと思う
getParam(**args)
で代替できるとコメントされている
def getParam(**args) case args in {zip: zip} p "zip" in {state: s, city: c} p "state+city" in { state: s} p "state" else raise 'need combination of zip, city, state' end end
- ただし、
**args
だとget_perdiem(nation: "usa")
がArgumentError
にならない問題はある - 要するに [Feature #17785]の
__params__
みたいなのがほしい!!! - なんか引数情報をメタ的に取得する機能がほしいなあ
[Bug #4443] odd evaluation order in a multiple assignment
- 以下のように多重代入した時に先に右辺のメソッドが呼び出されるというバグ報告
- Ruby は左から右に評価が進むので多重代入でもこれを期待する
- https://redmine.ruby-lang.org/issues/4440#note-1
def foo p :foo [] end def bar p :bar end # bar -> foo という順に評価される x, foo[0] = bar, 0 # output: # :bar # :foo # これは foo -> bar という順になる foo[0] = bar # output: # :foo # :bar
- 10年前のチケットで最近修正された
- 最新版だと以下のような動作になる
def foo p :foo [] end def bar p :bar end # 最新版だと foo -> bar と評価されるようになった x, foo[0] = bar, 0 # output: # :foo # :bar
[Feature #17398] SyntaxError in endless method
- 以下のようにエンドレスメソッド定義の本体が statement だった場合にエラーになる、というチケット
# OK def foo() = puts("bar") # syntax error, unexpected string literal, expecting `do' or '{' or '(' def hoge() = puts "bar"
- いくつか問題点があるが修正する方針で進めているらしい
- 現状提案されているパッチだと以下のような挙動になる
# current: SyntaxError # patch: Allow def foo() = puts "bar" # current: SyntaxError # patch: SyntaxError private def foo() = puts "bar"
[Feature #15198] Array#intersect?
Array#intersect?
を追加する提案- これは以下の動作と同じ意味
Array#&
[1, 1, 2, 3] & [3, 1, 4] #=> [1, 3]
(a1 & a2).any?
2021/04/18 今週の気になった bugs.ruby のチケット
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。
[Feature #17790] Have a way to clear a String without resetting its capacity
- 文字列の中身を空にする
String#clear
メソッドがある
str = "abc" str.clear p str # => ""
- この
String#clear
メソッドはcapacity
を含めて解放する- 参照:
String.new
- 参照:
require "objspace" str = String.new("homuhomu", capacity: 1024) puts ObjectSpace.dump(str) # => {"address":"0x55d9bdae3e80", "type":"STRING", "class":"0x55d9bd96a888", "bytesize":8, "capacity":1024, "value":"homuhomu", "encoding":"UTF-8", "memsize":1065, "flags":{"wb_protected":true}} # clear 後は capacity も含めて解放されている str.clear puts ObjectSpace.dump(str) # => {"address":"0x55d9bdae3e80", "type":"STRING", "class":"0x55d9bd96a888", "embedded":true, "bytesize":0, "value":"", "encoding":"UTF-8", "memsize":40, "flags":{"wb_protected":true}}
- これは例えば次のように同じバッファを使い回す時に解放されないほうがよいケースもある
buffer = String.new(encoding: Encoding::BINARY, capacity: 1024) 10.times do build_next_packet(buffer) udp_socket.send(buffer) buffer.clear end
- なので
clear
ではcapacity
を開放しないようにする、という提案 - 議論が進んでいて
clear(shrink: true/false)
みたいにclear
時に制御するのがいいんじゃない?みたいなコメントもでてる
[Feature #17785] Allow named parameters to be keywords
- 次のようにキーワード引数に『言語のキーワード』を指定すると参照するのが難しい
def check(arg, class:) # ここで class 引数を使いたいが class はキーワードなので参照できない arg.is_a?(class) end check(42, class: Integer) # => true
- これを
キーワード_
という名前で変数参照できるようにしよう、という提案
def check(arg, class:) # _ を付けて参照できるようにする arg.is_a?(class_) end check(42, class: Integer) # => true
- 一応
local_variable_get
で取得する事は可能ではあるが…
def check(arg, class:) # local_variable_get で動的に変数を取得する class_ = binding.local_variable_get(:class) arg.is_a?(binding.local_variable_get(:class)) end check(42, class: Integer) # => true
foo(class_, class:)
みたいな場合だと不都合じゃない?などコメントされている\class
みたいな書き方も提案されていて混沌としてる…
def check(arg, class:) arg.is_a?(\class) end # キーワード引数以外も適用可能 def diff(start, \end) \end - start end
[Feature #17786] Proposal: new "ends" keyword
- 複数の
end
をends
という1つのキーワードで定義できるようにしようという提案
def render(scene, image, screenWidth, screenHeight) screenHeight.times do |y| screenWidth.times do |x| color = self.traceRay(....) r, g, b = Color.toDrawingColor(color) image.set(x, y, StumpyCore::RGBA.from_rgb(r, g, b)) end end end
- これを以下のように記述する
def render(scene, image, screenWidth, screenHeight) screenHeight.times do |y| screenWidth.times do |x| color = self.traceRay(....) r, g, b = Color.toDrawingColor(color) image.set(x, y, StumpyCore::RGBA.from_rgb(r, g, b)) ends
- いろいろとコメントされているが流石に否定的な内容が目立つ…
- 例えば次のように途中に
ends
がある場合にclass A
のend
が不用意に閉じられて SyntaxError になる
class A def b c do d do e ends # ここで class A のスコープが閉じられる def c end end # なのでここで SyntaxError になる
- 『end を追加するエディタ使おう』って書かれてわろた
jq コマンドを使って Vim で選択した範囲の JSON をフォーマッティングする
書いてみた。
" 選択した範囲を jq コマンドを使ってフォーマッティングする " 範囲選択しない場合は現在の行を対象とする command! -range JSONFormatter :<line1>,<line2>!jq .
べんり。
別途 jq
外部コマンドが必要なので注意。