Crystal: `with self yield` inside constructor

Created on 20 May 2017  路  9Comments  路  Source: crystal-lang/crystal

Passing a block to a class constructor, invoking with self yield inside the constructor and trying to access an instance method errors with:

$ crystal block.cr
Error in block.cr:11: instantiating 'Foo:Class#new()'

Foo.new do
    ^~~

in block.cr:11: instantiating 'Foo:Class#new()'

Foo.new do
    ^~~

in block.cr:12: undefined local variable or method 'bar'

  bar
  ^~~

Expected behavior

$ crystal block.cr
bar

Source

class Foo
  def initialize(&blk)
    with self yield
  end

  def bar
    puts "bar"
  end
end

Foo.new do
  bar
end
$ crystal -v
Crystal 0.22.0 (2017-04-20) LLVM 4.0.0

play.crystal-lang.org/#/r/21wk

bug compiler

Most helpful comment

It's a known limitation. I don't know how to fix it, but PRs are welcome.

Workaround:

class Foo
  def self.new
    instance = new
    with instance yield
    instance
  end

  def bar
    puts "bar"
  end
end

Foo.new do
  bar
end

All 9 comments

It's a known limitation. I don't know how to fix it, but PRs are welcome.

Workaround:

class Foo
  def self.new
    instance = new
    with instance yield
    instance
  end

  def bar
    puts "bar"
  end
end

Foo.new do
  bar
end

@asterite what is the actual limitation here?

I have a similar issue :

Code

Not working code :

class Button
  class Config
    def initialize(@button : Button)
    end

    def on_click(&block : -> Void)
      @button.set_on_click &block
    end
  end

  @on_click = -> {}

  def initialize
    with Config.new(self) yield
  end

  protected def set_on_click(&block)
    @on_click = block
  end

  def on_click
    @on_click.call
  end
end

button =  Button.new do
  on_click { pp "Hello World" }
end

button.on_click
Error in line 26: instantiating 'Button.class#new()'

in line 13: too many block arguments (given 1, expected maximum 0)

Working work-around

class Button
  class Config
    def initialize(@button : Button)
    end

    def on_click(&block : -> Void)
      @button.set_on_click &block
    end
  end

  @on_click = ->{}

  def self.create
    btn = new
    with Config.new(btn) yield
    btn
  end

  protected def set_on_click(&@on_click)
  end

  def on_click
    @on_click.call
  end
end

button =  Button.create do
  on_click { pp "Hello World" }
end

button.on_click
"Hello World"

Technical information

$ crystal -v
Crystal 0.27.2 [60760a546] (2019-02-05)

LLVM: 4.0.0
Default target: x86_64-unknown-linux-gnu
$uname -a
Linux Keios 4.14.102-1-MANJARO #1 SMP PREEMPT Wed Feb 20 22:55:55 UTC 2019 x86_64 GNU/Linux

The limitation is what new method to generate when with... yield is used. If you can come up with something then it should be easy to implement.

I don't know how with ... yield is implemented, and I'm shared between the promises of https://forum.crystal-lang.org/t/rfc-with-yield-replacement/386 and diving into the impl of with yield..

I mean, every initialize also defines a self.new method. The definition is allocate, call initialize, return the instance. What would the self.new look like if the initialize had a with...yield in it? I proposed one expansion in one of the comments here, but that's not always the expansion that people want (the Config example above, for example)

Oh I see.. I think that would be solved by the with .. yield replacement from the forums by @bcardiff (btw I really like your &.{ ... } suggestion).

In the meantime I think we should at least display a proper error message, and workaround explanation.

I'm assuming its also a limitation that you can't use with self yield to set ivars? (Something like this)

https://play.crystal-lang.org/#/r/8930

class Foo
  property host : String
  property allow_creds : Bool = false

  def initialize(& : self -> Nil)
    with self yield
  end
end


foo = Foo.new do
  host = "google.com"
end

Error: instance variable '@host' of Foo was not initialized directly in all of the 'initialize' methods, rendering it nilable. Indirect initialization is not supported.

EDIT: Ofc this isn't going to work, just is an example.

For now we should disallow it with a proper error, so that it can be allowed later

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ArthurZ picture ArthurZ  路  3Comments

will picture will  路  3Comments

will picture will  路  3Comments

oprypin picture oprypin  路  3Comments

asterite picture asterite  路  3Comments