くりにっき

フルスタックキュアエンジニアです

至極の難問YAMLクイズ

前置き

  • 社内勉強会のLTで発表したら好評だったので投下
  • 自称YAMLエンジニアのsue445が今まで踏んだ罠をクイズにしました
  • Ruby 3.0.0の Psych で動作確認していますが他言語での挙動は調べていません
    • Psychがlibyamlベースなので他の言語のパーサでもだいたい同じ挙動をすると思うけど

練習問題

Q: 出力されるものは?

yaml = <<YAML
a: 1
YAML

YAML.load(yaml)
#=> ?
  1. {"a"=>"1"}
  2. {"a"=>1}
  3. シンタックスエラー
  4. その他

回答 2

YAMLの数字っぽい文字列はその言語の数字の型(Rubyだと Integer )として解釈されます。 文字列として解釈させたい場合は "1" (ダブルクオーテーション)や '1' (シングルクォーテーション)のように囲んでください

問題1

Q: 出力されるものは?

yaml = <<YAML
splash: ふたりはプリキュア Splash Star
yes: Yes!プリキュア5
yes_gogo: Yes!プリキュア5GoGo
fresh: フレッシュプリキュア!
YAML

data = YAML.load(yaml)
"#{data['splash']},#{data['yes']},#{data['yes_gogo']},#{data['fresh']}"
#=> ?
  1. "ふたりはプリキュア Splash Star,Yes!プリキュア5,Yes!プリキュア5GoGo,フレッシュプリキュア!"
  2. "ふたりはプリキュア Splash Star,,Yes!プリキュア5GoGo,フレッシュプリキュア!"
  3. ",Yes!プリキュア5,Yes!プリキュア5GoGo,フレッシュプリキュア!"
  4. シンタックスエラー

回答 2

true, false, yes, no, on, off はダブルクオーテーションなどで囲まない限りYAMLでは全て 真偽値 として扱われます

data = YAML.load(yaml)
=> {"splash"=>"ふたりはプリキュア Splash Star", true=>"Yes!プリキュア5GoGo!", "yes_gogo"=>"Yes!プリキュア5GoGo", "fresh"=>"フレッシュプリキュア!"}
data = YAML.load("{1: yes, 2: Yes, 3: on, 4: On, 5: true, 6: True}")
#=> {1=>true, 2=>true, 3=>true, 4=>true, 5=>true, 6=>true}

data = YAML.load("{1: no, 2: No, 3: off, 4: Off, 5: false, 6: False}")
#=> {1=>false, 2=>false, 3=>false, 4=>false, 5=>false, 6=>false}

問題2

Q: 出力されるものは?

yaml = <<YAML
default: &default
  slack:
    webhook_url: "https://example.com/"
    channel: "random"

production:
  <<: *default
  slack:
    channel: "production_notify"
YAML

data = YAML.load(yaml)
data["production"]
#=> ?
  1. {"slack"=>{"webhook_url"=>"https://example.com/", "channel"=>"random"}}
  2. {"slack"=>{"webhook_url"=>"https://example.com/", "channel"=>"production_notify"}}
  3. {"slack"=>{"channel"=>"production_notify"}}
  4. シンタックスエラー

回答 3

& (アンカー)と *エイリアス)で定義済みの値をいい感じに共通化できるのはYAMLのよくあるリファクタリング手法ですが、<<: (マージ)はdeep merge(要素内に別の要素があった時に再帰的にマージされる)ではなく第1要素だけを上書きするマージ(代入に近い)なので、今回の場合 default の内容が打ち消されます。

イメージ的にはこんな感じ

data["slack"] = {"webhook_url"=>"https://example.com/", "channel"=>"random"}
data["slack"] = {"channel"=>"production_notify"}

余談ですが https://github.com/railsware/global はdeep mergeしてくれるのがかなり便利で、前職のRailsアプリにはだいたい入っていました

問題3

Q: 出力されるものは?

yaml = <<YAML
go:
  - 1.9
  - 1.10
  - 1.11
  - 1.12
YAML

data = YAML.load(yaml)
data["go"]
#=> ?
  1. [1.9, 1.10, 1.11, 1.12]
  2. [1.9, 1.1, 1.11, 1.12]
  3. [1.90, 1.10, 1.11, 1.12]
  4. シンタックスエラー

回答 2

クオーテーションで囲んでいないので小数として解釈されるため 1.101.1 として解釈されます

厳密に 1.10 として評価するには "1.10" のように囲む必要があります。

余談ですがGo 1.10が出た時に .travis.yml1.10 を追加したらGo 1.1でCIが実行されてビルドが失敗したことがあります。

https://github.com/sue445/zatsu_monitor/commit/fc6b8ab806a3c48617cb084437d2d1c018777ee9#comments

問題4

Q: この中でシンタックスエラーになるのはどれか?

a

excludes:
  - *_test.rb

b

excludes:
  - test/**
  1. aのみシンタックスエラー
  2. bのみシンタックスエラー
  3. aとb両方シンタックスエラー
  4. シンタックスエラーは無い

回答 1

a

yaml = <<YAML
excludes:
  - *_test.rb
YAML

YAML.load(yaml)
Traceback (most recent call last):
        8: from /Users/sue445/.rbenv/versions/3.0.0/bin/irb:23:in `<main>'
        7: from /Users/sue445/.rbenv/versions/3.0.0/bin/irb:23:in `load'
        6: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.0/exe/irb:11:in `<top (required)>'
        5: from (irb):62:in `<main>'
        4: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/3.0.0/psych.rb:280:in `load'
        3: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/3.0.0/psych.rb:390:in `parse'
        2: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/3.0.0/psych.rb:456:in `parse_stream'
        1: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/3.0.0/psych.rb:456:in `parse'
Psych::SyntaxError ((<unknown>): did not find expected alphabetic or numeric character while scanning an alias at line 2 column 3)

b

yaml = <<YAML
excludes:
  - test/**
YAML

YAML.load(yaml)
#=> {"excludes"=>["test/**"]}

*_test.rb* が前述のエイリアスとして評価されるのですが、対応する _test.rb という名前のアンカーが存在しないためシンタックスエラーになります。

シンタックスエラーにしないためには "*_test.rb" のように囲む必要があります。

参考文献

おまけ:LT直後のみんなの反応

  • 「囲んでも囲まなくてもいいみたいなsyntaxやだ…」
  • 「問題として聞かれるとわかるけど、実際に yaml 書いてたらミスりそう」
  • YAMLの特別な挙動にうごかされないようにするのは、ダブルクォートつけるのがいいのかな。」
    • -> sue「『それはそう』なんですが、実際にそれやろうとするとかなり冗長になってだいぶ見づらくなるんですよね」
  • 6E-1 が(文字列として判定されてほしいのに) 指数表記で判定されてハマった」
  • YAMLほんとうにわかってなかった」