Crystal: `Proc(Nil)` in a method type restriction does not work like it does elsewhere

Created on 28 Mar 2020  Â·  11Comments  Â·  Source: crystal-lang/crystal

Added $170 bounty to this https://www.bountysource.com/issues/90719678-proc-nil-in-a-method-type-restriction-does-not-work-like-it-does-elsewhere

7527 the very handy ability to use Proc with a Nil return type restriction (e.g. Proc(Nil)) and have Procs that return non-nil values works

This allows you to do something like:

class ProcNil
  getter proc : Proc(Nil)

  def initialize
    @proc = -> { 123 }
  end
end

pp! ProcNil.new.proc.call # Which will return nil

The problem

Using a Proc(Nil) when restricting in a method argument does not work the same. It requires an explicit nil return value in the Proc

class ProcNil
  def initialize(proc : Proc(Nil))
  end
end

# Error: no overload matches 'ProcNil.new', proc: Proc(Int32)
#
# Overloads are:
#  - ProcNil.new(proc : Proc(Nil))
pp! ProcNil.new(proc: ->{123})

To get this to work you'd need to explicitly return nil:

ProcNil.new(proc: ->{123;nil}

Where the problem seems to be

I think the reason the version with getter works is that there is an odd behavior when using Proc(Nil) with a method

```crystal
def my_proc : Proc(Nil)
->{ 123 }
end

You'd expect this to be nil

pp! my_proc.call # 123

And this to be `Proc(Nil)

pp! typeof(my_proc) # Proc(Int32)
````

So it seems like this may be 2 issues. The type restriction in a method incorrectly return a Proc(Int32) even though the restriction is Proc(Nil). This by happenstance allows the first example to work

Fixing this

This is something I would really love to use in my Lucky projects to do stuff like this for generic HTML components where you can add Proc "slots" for passing in HTML https://gist.github.com/paulcsmith/ec853d33d1a0ef9b1eedd070e58136b9

So I've added a $170 bounty to this since I know I'm asking for a potentially complicated fix! :D https://www.bountysource.com/issues/90719678-proc-nil-in-a-method-type-restriction-does-not-work-like-it-does-elsewhere

My hope is this also helps someone during the crazy economy/COVID-19

feature topicsemantic

Most helpful comment

Thank you! It's fine, I don't need the bounty. Maybe give it to help fighting covid? Or maybe give it to Trump and convince him to enter total lockdown?

All 11 comments

Hi!

These are two separate issues.

The first one:

  1. passing Proc(Nil) to a Proc(Nil) restriction should work (also for more number of arguments, this is only about the return type)
  2. marking a method to return Proc(Nil) should make the method return Proc(Nil) regardless of whether it returns that, Proc(Int32), Proc(String), etc.

I'll fix 2. I'm not sure about 1. The only case where we do something like that is when the return type is Nil. But that's similar to returning void in other languages.

We _can_ implement point 2. What's the use case?

Why do:

def foo : Proc(Nil)
  ->{ 1 }
end

instead of:

def foo
  Proc(Nil).new { 1 }
end

Also, returning procs from methods isn't very common, so I'm very curious about the use case.

Hi Ari!

Sorry about the confusion! The issue I have is really about that first example in the problem section where I’d like to use a proc that has a non nil return value (like Proc(int32)) with a Proc(Nil) restriction. But maybe that’s weird. It appears to work in the first example and I think it is because it uses a getter and instance car with a type restriction instead of putting the type restriction in the method argument

The whole bit about restricting return type in a method to Proc(Nil) isn’t something I’ve ever done or needed. It was just me trying to see if that was maybe why the first example worked.

Does that make sense or did I just make it even more confusing? :P

But now that I think about it it is a bit strange to allow what I’m asking...I wonder why the first example works and the second doesn’t. Almost seems like neither one should work... 🤔

Maybe there is some kind of generic way of to say “I want a Proc and don’t care about the return type”? Like just Proc. And that’d allow a Proc with no args and any return type and will return nil. Maybe we need to chat because I’m not sure if I’m making any sense

I wonder why the first example works and the second doesn’t

The first example works because in #7527 we made the union of Proc(T) and Proc(Nil) to be Proc(Nil). So if you assign Proc(T) to something of type Proc(Nil), the result type is Proc(Nil) and that's exactly the type of the instance variable you are assigning to.

The second example doesn't work because type restriction logic is in a completely separate place in the compiler. Combining union types into other types is one thing. Restricting a type to enter into a method is another thing.

That second thing was overlooked in #7527 (by me ;-))

Also a reminder that #7527 introduced https://github.com/crystal-lang/crystal/issues/7698.

Yeah, I'll try to revert #7527 and only allow assigning Proc(T) to a Proc(Nil) instance/class var, or a var with a declared type. But the compiler won't merge Proc(T) | Proc(Nil) into Proc(Nil) anymore (if I can make it work).

Sounds good and thanks for the explanation Ary!

On Mar 28, 2020, at 1:55 PM, Ary Borenszweig notifications@github.com wrote:


Yeah, I'll try to revert #7527 and only allow assigning Proc(T) to a Proc(Nil) instance/class var, or a var with a declared type. But the compiler won't merge Proc(T) | Proc(Nil) into Proc(Nil) anymore (if I can make it work).

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub, or unsubscribe.

@asterite Thanks for fixing this! Remember to claim on https://www.bountysource.com/issues/90719678-proc-nil-in-a-method-type-restriction-does-not-work-like-it-does-elsewhere and then I'll confirm it!

Thank you! It's fine, I don't need the bounty. Maybe give it to help fighting covid? Or maybe give it to Trump and convince him to enter total lockdown?

Trust me I’d love to convince trump of lots of things. Will give it elsewhere. Thanks Ary!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

asterite picture asterite  Â·  3Comments

oprypin picture oprypin  Â·  3Comments

lbguilherme picture lbguilherme  Â·  3Comments

RX14 picture RX14  Â·  3Comments

ArthurZ picture ArthurZ  Â·  3Comments