From: knu@... Date: 2018-08-09T06:55:41+00:00 Subject: [ruby-dev:50616] [Ruby trunk Feature#14973] Proposal of percent literal to expand Hash Issue #14973 has been updated by knu (Akinori MUSHA). For debugging purposes, you could have a method like this: ```ruby class Binding def dump(*syms) pp syms.lazy.map { |sym| [sym, local_variable_get(sym)] }.to_h end end a = 1 b = 2 binding.dump(:a, :b) # prints "{:a=>1, :b=>2}" ``` ---------------------------------------- Feature #14973: Proposal of percent literal to expand Hash https://bugs.ruby-lang.org/issues/14973#change-73409 * Author: osyo (manga osyo) * Status: Open * Priority: Normal * Assignee: * Target version: ---------------------------------------- ## 概要 変数名から `{ 変数名: 変数の値 }` という Hash を定義するための %記法の提案です。 以前からちょくちょく[提案されていた](https://bugs.ruby-lang.org/issues/14579) ```ruby x = 1 y = 2 h = {x:, y:} p h #=> {:x=>1, :y=>2} ``` のような ES6 ライクな構文を `{}` 構文ではなくて %記法で定義するものになります。 ## 仕様 ```ruby hoge = 1 foo = 2 bar = 3 # スペース区切りの変数名をキー、変数の値を Hash の値として展開する %h(hoge foo bar) # => { hoge: 1, foo: 2, bar: 3 } ``` これは以下と同等の処理になります。 ```ruby hoge = 1 foo = 2 bar = 3 { hoge: eval("hoge"), foo: eval("foo"), bar: eval("bar") } # => { hoge: 1, foo: 2, bar: 3 } ``` ### ローカル変数以外 内部で `eval` を使用しているので、そのコンテキストで評価できればローカル変数以外も使用することが出来ます。 ```ruby def meth "meth" end @hoge = 42 Foo = "str" p %h(meth @hoge Foo $stdout) # => {:meth=>"meth", :@hoge=>42, :Foo=>"str", :$stdout=>#>} ``` キーは変数名そのままです(`$` や `@` がついたまま。 ### 重複したキーがある場合 キーが重複している場合、`{}` と同様に2回目以降は無視されます。 ```ruby hoge = 42 foo = "str" %h(hoge foo hoge) # => {:hoge=>42, :foo=>"str"} ``` ### キーの変数が存在しない場合 ```ruby hoge = 42 foo = "str" %h(hoge foo bar) # Error: undefined local variable or method `bar' for main:Object (NameError) ``` ### Hash 内で展開 Hash なので `**` で展開できます。 ```ruby hoge = 42 foo = "str" { bar: "bar", **%h(hoge foo) } # => {:bar=>"bar", :hoge=>42, :foo=>"str"} ``` ## 式展開 `%I` などと同様に式展開を行います。 ```ruby hoge = 42 foo = "hoge" %h(#{foo}) # => {:hoge=>42} ``` ## ユースケース ### キーワード引数に渡す ```ruby Model = Struct.new(:tag, :name, :age, keyword_init: true) def create_human(name:, age:) # ** で Hash を展開して渡す Model.new(tag: :human, **%h(name age)) # Model.new(tag: :human, name: name, age: age) end name = "mami" age = 15 # 変数をまとめて渡す create_human(%h(name age)) # => # # create_human(name: name, age: age) ``` ### デバッグ出力として利用する ```ruby class Hash # 適当なデバッグ出力用メソッド def debug_output each { |key, value| puts "#{key}: #{value}" } end end def create(name, age) # 引数を確認するためのデバッグ出力 %h(name age).debug_output end name = "homu" age = 14 create(name, age) # output: # name: homu # age: 14 ``` ## その他 * キーは Symbol で定義 → String 版もあったほうがよいだろうか * 式展開のみ実装 → `%i` みたいな式展開を行わないでほしいユースケースが思いつかなかった * なぜ `%h` という名前? → `Hash` だから… 以上、任意の変数名から Hash を展開する `%h` 記法の提案でした。 ご意見等あればコメント頂けると助かります。 ## 関連するチケット https://bugs.ruby-lang.org/issues/11105 https://bugs.ruby-lang.org/issues/14579 https://bugs.ruby-lang.org/issues/13137 ---Files-------------------------------- hash_expand.patch (7.3 KB) -- https://bugs.ruby-lang.org/