defx で絞り込みしつつ dentie-file/rec を起動する

defx で絞り込みしつつ dentie-file/rec を起動する defx で find っぽいことをしたかったので設定した。 単に :Denite file/rec -input=ファイル名 を起動しているだけ。 :Defx -auto-cd で起動しておく必要があるので注意。

" NOTE: auto-cd を有効にしておく必要があるので注意
call defx#custom#option('_', { 'auto_cd': 1, })


function! s:start_find()
    let find_word = input("Find Pattern: ")
    call denite#start([{ "name": "file/rec", "args": []}], { "input": find_word })
endfunction

autocmd FileType defx call s:defx_my_settings()
function! s:defx_my_settings() abort
    " 起動するキーマップ
    nnoremap <silent><buffer> gd :<C-u>call <SID>start_find()<CR>
endfunction

denite.nvim の denite-menu を使って簡単にファイルを開いたりする

dentie-menu を使うことで簡単に自分好みのショートカットを設定する source を定義することができます。

" denite-menu の設定
let s:menus = {}

" Denite menu:dotfile をすると開くファイルを登録する
let s:menus.dotfile = {
    \ 'description': 'Edit your dotfile'
    \ }
let s:menus.dotfile.file_candidates = [
    \ ['vimrc', '~/.vimrc'],
    \ ['gvimrc', '~/.gvimrc'],
    \ ['bashrc', '~/.bashrc'],
    \ ['bash_aliases', '~/.bash_aliases'],
    \ ]


" Denite menu:my_denites をすると開くファイルを登録する
" コマンドを呼ぶ
let s:menus.my_denites = {
    \ 'description': 'Denite list'
    \ }
let s:menus.my_denites.command_candidates = [
\    ['file/old', 'Denite file/old'],
\    ['menu:dotfile', 'Denite menu:dotofile'],
\    ['grep', 'Denite grep'],
\]

" 登録
call denite#custom#var('menu', 'menus', s:menus)

" 呼び出し
nnoremap <Space>ll :Denite menu<CR>

べんりべんり。

2021/03/25 今週の気になった bugs.ruby のチケット

内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。

[PR irb #212] Complete require and require_relative

  • irbrequire のファイル名補完をする機能の追加
irb(main):001:0" require "irb/<Tab>  # <- Tab を押すとファイル名を補完してくれる
  • これは普通に便利そう

[PR irb #204] Add whereami command

  • irbprywhereami コマンドを追加する PR
    • whereami すると binding.irb した付近のコードを確認することができる
$ ruby /tmp/a.rb

From: /tmp/a.rb @ line 3 :

    1: a = 1
    2: @b = 2
 => 3: binding.irb

irb(main)[01:0]> a
 => 1
irb(main)[02:0]> @b
 => 2
irb(main)[03:0]> whereami

From: /tmp/a.rb @ line 3 :

    1: a = 1
    2: @b = 2
 => 3: binding.irb

 => nil
  • pry の whereami コマンド知らなかった
  • これはめっちゃ便利そう…

[PR irb #203] Implement pry-like ls command

  • irbpryls コマンドを追加する PR
    • ls すると引数オブジェクトのメソッド一覧やインスタンス一覧を出力してくれる
$ irb
irb(main):001:0> require "erb"
 => true
irb(main):002:0> ls ERB.new('test')
ERB#methods: 
  def_class  def_method     def_module  encoding          filename  filename=    lineno  lineno=
  location=  make_compiler  result      result_with_hash  run       set_eoutvar  src   
instance variables: @_init  @encoding  @filename  @frozen_string  @lineno  @src
 => nil
  • これも知らなかった
  • めっちゃ便利そう…

[Feature #17411] Allow expressions in pattern matching

  • 以下のように ^ を使ってパターンマッチに式をかけるようにする提案
user = { name: "homu", age: 14 }
case user
# 式を書く場合は ^() を使う
in { age: ^(7 + 7) }
end

[Bug #17738] Ruby can still freeze ENV

  • ENV は普通は freeze できないががんばれば freeze できるという報告
# これは error
ENV.freeze

# こうすると freeze できる
Kernel.instance_method(:freeze).bind(ENV).call()
p ENV.frozen? #=> true
  • Ruby むずかしい…

[Bug #17735] Hash#transform_keys! drops non evaluated keys

  • 以下のように Hash#transform_valies! のブロック内で raise するとレシーバは変わらない
# transform_values! はレシーバの要素を保持する
hash = {a: 1, b: 2, c: 3}
a = hash.transform_values! { raise } rescue
p hash #=> {:a=>1, :b=>2, :c=>3}
  • しかし Hash#transform_keys! の場合はレシーバが空になる
# transform_keys! はレシーバの要素が空になる
hash = {a: 1, b: 2, c: 3}
hash.transform_keys!(){ raise } rescue
p hash #=> {}

[Bug #17736] Destructive methods inconsistently handle receiver frozen state in given block

  • Array#select! のブロック内でレシーバを freeze するとレシーバが空の配列になる
array = [1, 2, 3, 42]
array.select! do
  array.freeze
  false
end
p array #=> []
  • Array#uniq! の場合は例外になりレシーバはそのままになる
array = [1, 2, 3, 42, 2, 3]
begin
  array.uniq! do |item|
    array.freeze
    item
  end
rescue => err
  p err #=> #<FrozenError: can't modify frozen Array: [1, 2, 3, 42, 2, 3]>
end

p array #=> [1, 2, 3, 42, 2, 3]
  • Array#select! の方はバグということで修正された
array = [1, 2, 3, 42]
# Ruby 3.1.0-dev だとエラー
# error: `select!': can't modify frozen Array: [1, 2, 3, 42] (FrozenError)
array.select! do
  array.freeze
  false
end

[Bug #17739] Array#sort! changes the order even if the receiver raises FrozenError in given block

  • Array#sort! のブロック内でレシーバを freeze すると例外が発生するがソート済みになっているというバグ報告
array = [1, 2, 3, 4, 5]
begin
  array.sort! do |a, b|
    array.freeze if a == 3
    1
  end
rescue => err
  # 例外が発生する
  p err #=> #<FrozenError: can't modify frozen Array: [5, 4, 3, 2, 1]>
end

# 例外が発生してもソート済みになっている
p array #=> [5, 4, 3, 2, 1]
  • ちなみに break した場合はそこまでのソートになっている
array = [1, 2, 3, 4, 5]
array.sort! do |a, b|
  break if a == 3
  1
end

# 途中までソートされた状態
p array #=> [3, 4, 2, 1, 5]
  • これはまだ未修正

[Feature #17743] Show argument types in backtrace

  • バックトレースに引数情報も追加する提案
  • 例えば以下のようなコードを実行すると
def say_hi(person)
  puts message(person)
end

def message(person)
  "hi: #{person.name}"
end

say_hi(nil)
  • 以下のようなバックトレースが出力される
/tmp/vTaZxJg/70:6:in `message': undefined method `name' for nil:NilClass (NoMethodError)
    from hi.rb:2:in `say_hi'
    from hi.rb:9:in `<main>'
  • これを以下のようにする提案
hi.rb:6:in `message': undefined method `name' for nil:NilClass (NoMethodError)
    from hi.rb:2:in `say_hi' called with NilClass
    from hi.rb:9:in `<main>' called with NilClass
  • どのような表記にするのか(クラスだけ?値は必要がない?複数の引数の場合は?)やパフォーマンス的な懸念点がないか議論されている

[Feature #17744] Accumulate Enumerable#tally results

  • 以下のように Enumerable#tally に引数を渡してそれに結果を集計していくような提案
h = {}
[:a,:b,:c].tally(h)
[:a,:b,:d].tally(h)

# 2個の tally の結果が h に蓄積される
p h #=> {:a=>2, :b=>2, :c=>1, :d=>1}
  • 便利な気がするけど引数が変更されてしまうのはなんかちょっと気持ち悪い…

2021/03/18 今週の気になった bugs.ruby のチケット

内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。

[PR irb #202] process multi-line pastes as a single entity

  • 現行の irb だと以下のようなコードをペーストするとエラーになる
class A
  def a; self; end
  def b; true; end
end

a = A.new

a
 .a
 .b
irb(main):001:1* class A
irb(main):002:1*   def a; self; end
irb(main):003:1*   def b; true; end
irb(main):004:0> end
 = > :b
irb(main):005:0>
irb(main):006:0> a = A.new
 = > #<A:0x00005588503197b8>
irb(main):007:0>
irb(main):008:0> a
 = > #<A:0x00005588503197b8>
irb(main):009:0>  .a
/home/worker/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.4/lib/irb/workspace.rb:116:in `eval': (irb):9: syntax error, unexpected '.' (SyntaxError)
    from /home/worker/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.4/exe/irb:11:in `<top (required)>'
    from /home/worker/.rbenv/versions/3.0.0/bin/irb:23:in `load'
    from /home/worker/.rbenv/versions/3.0.0/bin/irb:23:in `<main>'
irb(main):010:0>  .b
  • これはペーストしたタイミングで Ruby のコードが逐次的に評価され .a を呼び出した時にエラーになってしまっているため
    • 先に a のコードが評価され
  • これを回避するためにペーストして『最後に』コードを評価するようにする PR
  • 個人的には落とし所としてはいいとは思う

[Bug #17571] prependしたArray#[] が反映されない

  • 以下のように Arrayprepend してる #[] が呼び出されないことがある
module TestMod
  def [](*)
    :called
  end
end
Array.prepend TestMod

# これは Array#[] が呼ばれる
p [1, 2, 3][1]
# => 2

# これは TestMod#[] が呼ばれる
p [1, 2, 3][]
# => :called

# Method オブジェクトは TestMod を指している
p [1, 2, 3].method(:[])
# => #<Method: Array(TestMod)#[](*) /tmp/vud3mdg/27:2>
  • これはおそらく Array#[] を事前にメソッドキャッシュしておりそちらを優先して呼び出しているのが原因ぽい?
    • なので prepend しているメソッドは呼ばれなくなっている
    • 引数がない場合は Array#[]シグネチャが異なるのでキャッシュされたメソッドではなくて prepend されたメソッドを呼び出しているので意図する挙動になっているみたい?
  • これは Ruby 2.7 では問題なくて Ruby 3.0 から問題になっている
    • Ruby 3.0 に上げるタイミングでなにか壊れるかもしれないので注意したい
  • 原因っぽいコミット(と PR)

[Bug #17725] Prepend breaks ability to override optimized methods

  • 以下のように String.prepend すると String#+ が上書きされたりされなかったりする
# これは上書きされる
class String
  def + other
    'blah blah'
  end
end

p 'a' + 'b'
# => "blah blah"
# これは上書きされない
String.prepend(Module.new)
class String
  def + other
    'blah blah'
  end
end

p 'a' + 'b'
# => "ab"
  • これは Ruby 3.0 で再現する
    • Ruby 2.7 では再現しない
    • Ruby 3.0 に上げると急にぶっ壊れるかもしれないので注意する…

[Bug #16996] Hash should avoid doing unnecessary rehash

[Bug #17719] Irregular evaluation order in hash literals

  • Hash リテラルでキーが重複している場合に以下のような評価順になる
# 1個目と2個目の foo の要素が先に評価される
$ ruby -e '{foo:p(1), bar:p(2), foo:p(3)}'
-e:1: warning: key :foo is duplicated and overwritten on line 1
1
3
2

Ruby の Hash リテラルでキーが重複している場合の奇妙な動作

最近こういう動作を見つけたので覚書。

Hash リテラルでキーが重複しているとどうなるのか

Hash リテラルで Hash を定義した場合、キーの順番は『記述した順番』になります。

# ここで定義した順番になる
hash = { age: 14, name: "homu" }
pp hash
# => {:age=>14, :name=>"homu"}

hash = { name: "homu", age: 14 }
pp hash
# => {:name=>"homu", :age=>14}

では、この Hash リテラルでキーが重複しているとどうなるのかというと次のようになります。

# age が重複している場合
hash = { id: 1, age: 14, name: "homu", age: 20, age: 10 }
# キーの順番は age が先にくるが、値は最後に定義した値になる
pp hash
# => {:id=>1, :age=>10, :name=>"homu"}

こんな感じで

  • キーの位置は『最初に定義された位置』
  • キーの値は『最後に定義された値』

という挙動になります。
キーの位置と値が異なるのでちょっと混乱しますね。
このような書き方をすると警告もでますし、基本的に Hash リテラルではキーを重複して書かないようにしましょう。

# warning: key :age is duplicated and overwritten on line 2
hash = { id: 1, age: 14, name: "homu", age: 20, age: 10 }

関連

2021/03/11 今週の気になった bugs.ruby のチケット

内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。

[Bug #16908] Strange behaviour of Hash#shift when used with default_proc.

  • 以下のように default_proc が設定されている状態で Hash#shift を呼ぶといと意図しない値が返ってくるよっていうチケット
    • Hash が空の時に shift を呼ぶと default_proc の値が返ってくる
hash = Hash.new{|k,v| k[v] = 0}

hash.shift # => 0
hash.shift # => [nil, 0]
  • 意図としては両方共 [nil, 0] が返ってきてほしい
  • もうちょっと詳細に説明するとこんな感じ
hash = Hash.new{|k,v| k[v] = 0}

# 空
p hash       # => {}

# ここは default_proc 値を返す
# ここが意図していないというチケット
p hash.shift # => 0

# hash,shift 後は中身が入ってる状態になる
p hash       # => {nil=>0}
# ので、これは [nil, 0] を返す
p hash.shift # => [nil, 0]
  • この挙動は確かに奇妙
  • チケットだと nil を返すほうがいい、みたいな意見もある

[Feature #17674] Proposal: Method#source_location or Method#owner for refined methods

# test.rb
module Cover
  refine Range do
    def cover?(value)
      return super unless value.is_a?(Range)

      super(value.first) && super(value.last)
    end
  end
end
using Cover

pp (1..10).method(:cover?).source_location
# Ruby 2.6 => nil
# Ruby 2.7 => ["/test.rb", 4]

pp (1..10).method(:cover?).owner
# Ruby 2.6 => Range
# Ruby 2.7 => #<refinement:Range@Cover>

2021/03/05 今週の気になった bugs.ruby のチケット

内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。

[Feature #17660] Expose information about which basic methods have been redefined

  • RubyVM.redefined_methods というメソッドを追加する提案
  • これは再定義されてた標準的なメソッドの一覧を返すようなメソッドになる
class Integer
  def +(x); x ** self; end
end

# 再定義された Integer#+ メソッドの情報を返す
p RubyVM.redefined_methods # => {Integer=>[:+]}
  • これによって再定義されてしまうことを防ぐことができるらしい
Minitest.after_run {
  fail "Basic methods have been redefine" if RubyVM.redefined_methods.any?
}

[Bug #17652] GC compaction crash on mprotect

  • GC compaction でクラッシュするというバグ報告
  • 以下のコードで再現
GC.auto_compact = true

times = 20_000_000
arr = Array.new(times)
times.times do |i|
  arr[i] = "#{i}"
end

arr = Array.new(1_000_000, 42)
GC.start

puts "ok"
  • こんなコードで再現するんだ… GC なんもわからねえ…

[Feature #17663] Enumerator#with, an alternative to Enumerator#with_object

  • Enumerator#with を追加する提案
  • #with に渡した引数を #with のブロックの引数として受け取る
    • Enumerator#with と似ているが戻り値がちょっと違う
class Enumerator
  def with(*options)
    return to_enum(:with, *options) unless defined? yield

    each do |entry|
      yield entry, *options
    end
  end
end


# each_with_object は each_with_object の引数を返す
pp (1..5).map.each_with_object(2) { |it, n| p it.to_s(n) }
# => 2

# with はそのままイテレーションの結果を返す
pp (1..5).map.with(2) { |it, n| it.to_s(n) }
# => ["1", "10", "11", "100", "101"]

# こうもかける
pp (1..5).map.with(2, &:to_s)
# => ["1", "10", "11", "100", "101"]
  • #each_with_object#map などでチェーンしたい場合はあるのでこれは普通に便利そう
  • まあ今なら素直にナンパラを使えばいいじゃん、という気もするが…
    • (1..5).map { _1.to_s(2) }

[Misc #17662] The heredoc pattern used in tests does not syntax highlight correctly in many editors

assert_ruby_status([], "#{<<-"begin;"}\n#{<<-'end;'}", bug)
begin;
  exit("1" == Thread.start(1, &:to_s).value)
end;
  • これは以下のような文字列になる
str = "#{<<-"begin;"}\n#{<<-'end;'}"
begin;
  exit("1" == Thread.start(1, &:to_s).value)
end;
pp str
# => "\n" + "  exit(\"1\" == Thread.start(1, &:to_s).value)\n"
  • この書き方だとエディタでハイライトされないことが多いので以下のように書き直そう、という提案
assert_ruby_status([], <<~'RUBY', bug)
  exit("1" == Thread.start(1, &:to_s).value)
RUBY
  • 手元の Vim だとうまくハイライトされなかった…うーん…

[Bug #17661] IO#each will segfault when if file is closed inside an each_byte block

  • 以下のように File#each_byte 内で File#close すると segv するというバグ報告
file = File.open(__FILE__)
file.each_byte do |byte|
  file.close
end

[Bug #17667] Module#name needs synchronization

  • Module#name は非同期処理に対応していないので以下のようにすると segv するというバグ報告
class C
  @iv = 1
end
Ractor.new {
  loop {
    C.name
  }
}

class C
  0.step { |i|
    instance_variable_set("@iv#{i}", i)
  }
end
  • Module#name 内でインスタンス変数にアクセスしたりしているんですかね?普通に踏みそう