EDIT: Technically a && a.upcase, but that’s not very common and #try is simpler to grok.
EDIT2: It’s also usually ideal if you can just remove Nil from the type of a variable entirely. However depending on your use case/context this may not ofc be possible.
Yes, I try to avoid nil as much as possible but when it comes to external data it’s sometimes impossible.
For example, if in this YAML data I don’t know if the keys foo, bar and baz are present, the try chain become quite long:
require "yaml"
data = YAML.parse <<-YAML
---
foo:
bar:
baz:
- qux
- fox
YAML
pp data["foo"]?.try &.["bar"]?.try &.["baz"]?.try &.[1].as_s
Do you know what the data structure is ahead of time? Like its schema? You’d have to know what you’re parsing and it’s possible those keys would always be there? Like in your example there’s no reason to check if foo, bar, and baz are nil since they’re clearly not?
I’d recommend defining your own types using YAML::Serializable:
require "yaml"
struct Data
include YAML::Serializable
getter foo : Foo
end
struct Foo
include YAML::Serializable
getter bar : Bar
end
struct Bar
include YAML::Serializable
getter baz : Array(String)
end
yaml = <<-YAML
---
foo:
bar:
baz:
- qux
- fox
YAML
data = Data.from_yaml(yaml) # => Data(@foo=Foo(@bar=Bar(@baz=["qux", "fox"])))
data.foo.bar.baz[1] # => "fox"
For this specific use case dig (or JSON::Serializable) should be viable alternatives.
There had been a general discussion about a safe navigation operator, yet it didn’t seem worth the effort for very little gain over the simplicity and concisencess of .try.
And from_yaml will throw an error if the YAML doesn’t match the classes you’re mapping it into, so you’ve basically validated that you have what you need in a single line of code. Simplifes the code that comes after.