Shortcut for `try(&)`

In Crystal to deal with nilable type we use try(&):

a = rand < 0.5 ? "abc" : nil
pp a.try &.upcase

I found myself using try quite often, so I was wondering if there wasn’t a simpler syntax like in Ruby?

a = rand < 0.5 ? "abc" : nil
puts a &.upcase

There is not.

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.

Too bad :cry:

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?

dig and dig? Might be handy in this situation

2 Likes

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"
2 Likes

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.

2 Likes

Thanks @bcardiff and @jgaskins for your suggestions, YAML::Serializable perfectly fits my needs.

1 Like

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.

2 Likes