Learning macro

while reading
https://crystal-lang.org/reference/1.10/syntax_and_semantics/macros/index.html#scope

They can also be defined in classes and modules, and are visible in those scopes. Macros are also looked-up in the ancestors chain (superclasses and included modules).

class Foo
  macro emphasize(value)
      "***#{ {{value}} }***"
  end
end

Foo.emphasize(10) # => "***10***"

it seemed only Foo.emphasize , it’s more like a class method,
when I change to

Foo.new.emphasize(10)

, then it reports error,
which is confusing to me?

I’m sorry that it’s confusing to you, but that’s the way it works :person_shrugging:. Macros defined within a type act like class methods, not instance methods.

visible in those scopes means they’re implicitly visible from within those scopes. So this works: #foo is instance scope of Foo but .emphasize is defined in the class scope.

class Foo
  macro emphasize(value)
      "***#{ {{value}} }***"
  end

  def foo
    emphasize(10)
  end
end

Foo.new.foo

You cannot explicitly address features from the class with an instance receiver.

but using with … yield example, it can use instances?

class Bar
  property x  = 1
  macro emphasize(value)

        "bar#{ {{value}} }bar"
  end
end
class Foo
  property x  = 2
  macro emphasize(value)
      "foo#{ {{value}} }foo"
  end

  def yield_with_self(&)
    with Bar.new yield
    with Bar yield

    with Foo yield
    with Foo.new yield

  end
end

Foo.new.yield_with_self { puts emphasize(10) } # => "***10***"

it outputs

bar10bar
bar10bar
bar10bar
bar10bar

so first, in with … yield, it seemed instance can work? with Foo.new yield,
it’s instance scope, but the macro is defined in class scope?

second, why it outputs bar10bar 4 times, it seemed wrong…
the calling with Foo yield and with Foo.new yield seemed disappear…

Hello, confusion about this?

If I had to guess, I’d say it’s because of what @straight-shoota said. I.e. that with with Bar.new yield the block is invoked within the scope of Bar so its able to find the macro. with Bar yield is similar because the macro is available in the class scope as well so it works there too.

Hello, the calling site is:

with Bar.new yield
with Bar yield

with Foo yield
with Foo.new yield

you can see I mix Bar and Foo,

but output

bar10bar
bar10bar
bar10bar
bar10bar

it seemed like a bug? where does with Foo yield go?

Oh, I see what you mean now. That does seem a bit unexpected.

while reading this

the last paragraph,

In macro definitions, arguments are passed as their AST nodes, giving you access to them in macro expansions ({{some_macro_argument}} ). However that is not true for macro defs. Here the parameter list is that of the method generated by the macro def. You cannot access the call arguments during compile-time.

this sentence I’m quite confusing,
Here the parameter list is that of the method generated by the macro def,
I read it several times, what does that mean?

class Person
  def initialize(@name : String, @age : Int32)
    end
end

class Object
  def has_instance_var?(name) : Bool
  # We cannot access name inside the macro expansion here,
  # instead we need to use the macro language to construct an array
  # and do the inclusion check at runtime.

    {{ @type.instance_vars.map &.name.stringify }}.includes? name
    {% debug %}
    {{ @type.instance_vars.map &.name.stringify }}.includes? name
  end
end

person = Person.new "John", 30
person.has_instance_var?("name")     # => true
person.has_instance_var?("birthday") # => false

also how do I debug macro def, it seemed {% debug %} outputs nothing compared to normal macro?

It means that every subclass of Object will have a has_instance_var? method created, with the {{ @type.instance_vars.map &.name.stringify }} expanded to be an array literal of the names of the ivars on that type. The arguments passed to the method are not available at compile time unlike they would if it was macro has_instance_var?(name).

It works if you wrap it in a {% begin %}/{% end %}:

{% begin %}
  {{ @type.instance_vars.map &.name.stringify }}.includes? name
  {% debug %}
{% end %}