Crystal: Recursive blocks?

Created on 29 May 2015  路  6Comments  路  Source: crystal-lang/crystal

Disclaimer: I am new to Crystal (and Ruby), so I might be missing something.

I tried to create a recursive block/proc:

def f
    g = ->(n: Int){ g n-1 }
    g
end

f

That didn't work with an error about an undefined reference to g. Understandable. However, things then start getting odd. I figured I could declare g as nil and then re-assign it:

def f
    g = nil
    g = ->(n: Int){ g n-1 }
    g
end

f

I still got an undefined reference error! But this:

def h
    yield n
end

def f
    g = nil
    g = ->(n: Int){ h &g; g n-1 }
    g
end

f

gives:

Error in ./rec.cr:26: instantiating 'f()'

f
^

in ./rec.cr:22: expected a function type, not Nil

    g = ->(n: Int){ h &g; g n-1 }

Right. Forgot about Nil checks. However, changing g's body to:

g = ->(n: Int){ h &g if g; g n-1 }

gives the exact same error. I can't put a def inside of a def either. IS THERE SOME WAY TO SOLVE THIS?? I feel at least the errors should be more consistent (why is g not defined when being called but defined when being passed to a function, but still has type Nil even when suffixed with an if guard?).

feature compiler

Most helpful comment

Thanks to @waterlink in #2218 you can actually do this, and there's no need for a new keyword:

def f
  g = uninitialized Int32 -> Int32
  g = ->(n : Int32) do
    if n <= 1
      1
    else
      n * g.call(n - 1)
    end
  end
  g
end

10.times do |i|
  p f.call(i)
end

In play.crystal-lang.org

Not super pretty, you have to use uninitialized, but it gets the job done. And since I think this need isn't very common, it's better to keep things simple for now.

All 6 comments

BTW, I solved this by doing:

g = ->(n: Int) { 0 }
g = ->(n: Int){ g n-1 }

Wait, I think I got it. g is of type Nil in one case because it hasn't been defined yet. The other error is more helpful.

Still leaving this open because:

  • The ability to define a recursive block without the ugly workaround is valuable.
  • The error messages could be more consistent with regard to being either Nil or undefined.

@kirbyfan64 Sorry, but most of the snippets don't compile. For example g n-1 doesn't compile, to invoke a Proc you need to do g.call(n-1).

We discussed this with @waj some days ago, we'll probably need some keyword to refer to the current Proc (other than self, of course) to implement this, but I don't think this is something that's really useful, you can always workaround it with other solutions. But I'll leave this open as an enhancement.

(proposed: clojure's recur)

Thanks to @waterlink in #2218 you can actually do this, and there's no need for a new keyword:

def f
  g = uninitialized Int32 -> Int32
  g = ->(n : Int32) do
    if n <= 1
      1
    else
      n * g.call(n - 1)
    end
  end
  g
end

10.times do |i|
  p f.call(i)
end

In play.crystal-lang.org

Not super pretty, you have to use uninitialized, but it gets the job done. And since I think this need isn't very common, it's better to keep things simple for now.

Well, I guess it's not used a lot in some styles of programming, but it's very functional-ish :).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

oprypin picture oprypin  路  3Comments

RX14 picture RX14  路  3Comments

cjgajard picture cjgajard  路  3Comments

oprypin picture oprypin  路  3Comments

asterite picture asterite  路  3Comments