Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metaprogramming: type inference degrades classes to their parents #448

Closed
ysbaddaden opened this issue Feb 25, 2015 · 6 comments
Closed

Comments

@ysbaddaden
Copy link
Contributor

Maybe this is a duplicate #423 and I don't think the issue title is to the point of what's actually going on.

The following will work as expected:

class A
  @@children = [] of A

  def self.children
    @@children
  end

  macro inherited
    # here self is always correct: B, C or D
    A.children << self.new
  end
end

# p A.children

class B < A; end
class C < A; end
class D < C; end

p A.children # => [B, C, D]

But if I uncomment the p A.children before I define B, C, D then both will print [B, B, B]. I think the compiler is trying to determinate the contents of A.children before it's given a chance to actually have some content, and thus defaults to the first case, then skips the others.

A current working solution is a mix of using macros and avoiding to define the types too early (by not inspecting them for instance:

class A
  # ...

  macro def foo : Nil
    p self
  end
end

A.children.each(&.foo)  # => B, C, D

class B < A; end
class C < A; end
class D < C; end

p A.children # => [B, C, D]
@ysbaddaden ysbaddaden changed the title Type inference degrades classes to their parents Mtaprogramming: type inference degrades classes to their parents Feb 25, 2015
@ysbaddaden ysbaddaden changed the title Mtaprogramming: type inference degrades classes to their parents Metaprogramming: type inference degrades classes to their parents Feb 25, 2015
@asterite
Copy link
Member

Now it works :-)

macro defs are hard...

@ysbaddaden
Copy link
Contributor Author

The whole macro feature look very hard, but damn, there is everything needed for great metaprogramming at compile time! I was blazed to find inherited, included, extended and all the other macros.

Macro defs are great: a generic method that actually generates the method for each subclass. Mind blows :p

@kostya
Copy link
Contributor

kostya commented Feb 25, 2015

what is macro defs?

@asterite
Copy link
Member

@kostya They are not (yet) documented, but are used across the standard library in some places.

A macro def is a def whose body is a macro, and is instantiated for each type (when needed). They are mostly useful when combined with compile-time reflection. For example:

class Object
  macro def instance_vars : Array(String)
    [{{ *@type.instance_vars.map &.stringify }}] of String
  end
end

puts "hello".instance_vars #=> ["bytesize", "length", "c"]

record Point, x, y

puts Point.new(1, 2).instance_vars #=> ["x", "y"]

puts 1.instance_vars #=> []

We use this in Reference#inspect(io), to show the class name and the contents of each instance variable. For a Struct we can automatically generate a hash method or ==. Or to get all possible names of an enum.

The good thing about macro defs is that they are instantiated at needed. This means that if you never use Point#inspect, no code will be generated for that. Ruby implements "inspect" by keeping the instance variable information at runtime. The downside of this is that it consumes more memory, and this information has to be available for every object, even if at runtime it's not used.

In Crystal we try to compute a lot of information at compile time, so at runtime things will be faster and occupy less memory. We might add reflection at a later point, but for now we try to do as much as possible at compile time. Other languages (notably D and Nim) try to do the same, and I like those too :-)

@ysbaddaden
Copy link
Contributor Author

Copy-Paste to crystal book :-)

@asterite
Copy link
Member

@ysbaddaden You are right, I should put that there and here provide a link... I'll start doing that 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants