Ask some questions, thanks (Again)

  1. Is there possible to check if previous_def exists? to avoid the code raise exception.
def meth
  previous_def
 # ...
end

# Raise: Error: there is no previous definition of 'meth'

  1. What is the equivalent of BASH exec in Crystal? same in Ruby is Kernel#exec.
 ╰─ $ \cat 1.sh
exec sleep 100
 
 ╰─ $ sh 1.sh &
[1] 95600

╰─ $ ps aux |grep 1.sh
 

 ╰─ $ ps aux |grep sleep
zw963      95600  0.0  0.0   8608   960 pts/1    S    02:19   0:00 sleep 100

  1. The following code not raise a error on compile-time, i consider check use a missing defined const is possible, right? for following code, what i expected is, raise a exception to tell me use a not defined const.
class A
  def meth
    MissingClass.new
  end
end

a = A.new

# no error

  1. Is there a way to make #try chainable?
   class A
  def meth1
    nil
  end
end

a = A.new
p a.try &.meth.upcase.downcase

Above code raise following exception.

In 1.cr:10:16

 10 | p a.try &.meth.upcase
                     ^-----
Error: undefined method 'upcase' for Nil

But, what i expected is, it only return nil with no exception raised.
The key point is, if the first meth1 is nil, all latter should be skipping.

In fact, Dart use this hack. check this:

EDIT: In fact, is there a more simpler way to use #try make following code work?

y = if rand > 0.5
      [[1, 2, 3]]
    else
      nil
    end

y.first.each do |e|
  p e
end

# Error: undefined method 'first' for Nil (compile-time type is (Array(Array(Int32)) | Nil))

Following code is work, but too verbose.

y.try do |e1|
  e1.first.try do |e2|
    e2.each do |e|
      p e
    end
  end
end

If use ruby, code is more simple.

y&.first&.each do |e|
  p e
end

Probably depends on the exact context. What are you wanting to do?

Process.exec

You never call #meth, so the compiler tree shakes it out of the compiled binary. This is, IMO, why tests are still important in Crystal. Otherwise it would be possible for an end user to go use a method of your library and find it fails due to a typo/bug in the method. If that method had test coverage, you would have found it earlier.

Wouldn’t this just be a.try &.meth1.try &.upcase.downcase?

y = if rand > 0.5
      [[1, 2, 3]]
    else
      nil
    end

y.try &.first.each do |e|
  p e
end

Never mind, what i expect is, write a method, invoke previous_def if available, but if previous def is not exists, no raise error.

But, it is a not defined const, we can even visit the the const in macro at the compile time.
so, i guess it should be possible to find this const missing at compile time?

Sorry, above example doesn’t express what I mean.

y = if rand > 0.5
      if rand > 0.5
        [[1, 2, 3]]
      else
        [nil]
      end
    else
      nil
    end

# following example not work.
y.try &.first.each do |e|
  p e
end

The gist of the issue is that you never call #meth so it’s unused. Because it’s unused it’s not included in the final binary. There are various threads/issues about this. E.g. Unused code is not checked by the compiler · Issue #4402 · crystal-lang/crystal · GitHub.

Right, because in this case the .try on y ensures y doesn’t return nil. But then the .first call returns Nil | Array(Int32), so it fails. You’d have to add another .try on the response of .first to handle that. I.e. y.try &.first.try &.each. Of course there are other ways to handle nil that could make this more readable. E.g. maybe something like:

if y && (v = y.first)
  v.each { |e| p e }
end

Yes, i guess i see the point.

But, following code will raise error, even meth1 never be called.

def meth1
  MissingConst
end

x = ->meth1

Could you please explain on this?


Oops, i probably use try like y.try(&.first).try(&.each) do |e| before, that not work.

 11 | y.try(&.first).try(&.each) do |e|
                                   ^
Error: block already specified with &

I consider write code like this quite fantastic, it execute from right to left, and can’t include parenthesis.

y.try &.first.try &.each do |e|
  p e
end

Well in this case it is being used, even if your local x variable isn’t. I guess there are just certain exceptions/limitations in what the compiler is able to safely ignore.

To type x = ->meth the compiler needs to know what’s the type of meth. And that’s where it’s typed and the constant is checked and the error is produced.

So maybe the explanation isn’t “when the method is unused” but “when the method doesn’t need to be typed” (that usually means it’s unused)

3 Likes