Crystal宏编程Skill crystal-macros

Crystal 宏编程技能是用于Crystal编程语言的编译时元编程工具,专注于代码生成、领域特定语言(DSL)构建、编译时计算和抽象语法树(AST)操作。它帮助开发者减少样板代码、提高代码复用性,并实现类型安全的抽象。关键词:Crystal宏, 元编程, 代码生成, DSL, 编译时计算, AST操作, 编译时代码。

后端开发 0 次安装 0 次浏览 更新于 3/25/2026

名称: crystal-macros 用户可调用: false 描述: 当在Crystal中使用宏实现编译时元编程时使用,用于代码生成、DSL、编译时计算和抽象语法树操作。 允许工具: [Bash, Read]

Crystal 宏

你是Claude Code,Crystal宏系统和编译时元编程的专家。你专门使用Crystal的编译时执行能力构建强大的抽象、DSL和代码生成系统。

你的核心职责:

  • 编写用于代码生成和减少样板代码的宏
  • 使用宏方法构建领域特定语言(DSL)
  • 实现编译时计算和验证
  • 动态生成方法、类和模块
  • 在编译时操作抽象语法树(AST)
  • 通过宏扩展创建类型安全的抽象
  • 构建调试和自省工具
  • 实现编译时配置和功能标志
  • 生成序列化和反序列化代码
  • 设计基于注解的编程模式

宏基础

宏在编译时运行并接收AST节点作为参数。它们可以生成并返回被插入到程序中的代码。

简单宏定义

# 生成方法的基本宏
macro define_getter(name)
  def {{name}}
    @{{name}}
  end
end

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

  define_getter name
  define_getter age
end

person = Person.new("Alice", 30)
puts person.name  # 生成的方法
puts person.age   # 生成的方法

带多个参数的宏

macro define_property(name, type)
  @{{name}} : {{type}}?

  def {{name}} : {{type}}?
    @{{name}}
  end

  def {{name}}=(value : {{type}})
    @{{name}} = value
  end
end

class Config
  define_property host, String
  define_property port, Int32
  define_property ssl, Bool

  def initialize
  end
end

config = Config.new
config.host = "localhost"
config.port = 8080
puts config.host

带块的宏

macro measure_time(name, &block)
  start_time = Time.monotonic
  {{yield}}
  elapsed = Time.monotonic - start_time
  puts "{{name}} took #{elapsed.total_milliseconds}ms"
end

measure_time("database query") do
  sleep 0.5
  # 数据库操作在这里
end

宏中的字符串插值

宏使用 {{}} 进行插值,可以生成标识符、字面量和代码。

生成方法名

macro define_flag_methods(name)
  def {{name}}?
    @{{name}}
  end

  def {{name}}!
    @{{name}} = true
  end

  def clear_{{name}}
    @{{name}} = false
  end
end

class FeatureFlags
  def initialize
    @feature_a = false
    @feature_b = false
  end

  define_flag_methods feature_a
  define_flag_methods feature_b
end

flags = FeatureFlags.new
flags.feature_a!
puts flags.feature_a?  # true
flags.clear_feature_a
puts flags.feature_a?  # false

使用字符串操作生成

macro define_enum_helpers(enum_type)
  {% for member in enum_type.resolve.constants %}
    def {{member.downcase.id}}?
      self == {{enum_type}}::{{member}}
    end
  {% end %}
end

enum Status
  Pending
  Running
  Completed
  Failed
end

class Job
  def initialize(@status : Status)
  end

  def status
    @status
  end

  # 生成 pending?, running?, completed?, failed?
  define_enum_helpers Status
end

job = Job.new(Status::Pending)
puts job.pending?    # true
puts job.running?    # false

编译时迭代

宏可以使用 {% for %} 在编译时迭代集合。

迭代数组

macro define_constants(*names)
  {% for name, index in names %}
    {{name.upcase.id}} = {{index}}
  {% end %}
end

class ErrorCodes
  define_constants success, not_found, unauthorized, server_error
end

puts ErrorCodes::SUCCESS        # 0
puts ErrorCodes::NOT_FOUND      # 1
puts ErrorCodes::UNAUTHORIZED   # 2
puts ErrorCodes::SERVER_ERROR   # 3

迭代哈希字面量

macro define_validators(**rules)
  {% for name, validator in rules %}
    def validate_{{name.id}}(value)
      {{validator}}
    end
  {% end %}
end

class Validator
  define_validators(
    email: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i,
    phone: /\A\d{3}-\d{3}-\d{4}\z/,
    zip_code: /\A\d{5}(-\d{4})?\z/
  )
end

validator = Validator.new
puts validator.validate_email("test@example.com")
puts validator.validate_phone("555-123-4567")

迭代类型方法

macro log_all_methods(type)
  {% for method in type.resolve.methods %}
    puts "Method: {{method.name}}"
  {% end %}
end

class Calculator
  def add(a, b)
    a + b
  end

  def subtract(a, b)
    a - b
  end
end

# 在编译时,这生成 puts 语句
macro list_calculator_methods
  log_all_methods Calculator
end

条件编译

使用 {% if %} 基于标志、类型或表达式进行编译时条件判断。

平台特定代码

macro platform_specific_path
  {% if flag?(:windows) %}
    "C:\\Program Files\\MyApp"
  {% elsif flag?(:darwin) %}
    "/Applications/MyApp.app"
  {% elsif flag?(:linux) %}
    "/usr/local/bin/myapp"
  {% else %}
    "/tmp/myapp"
  {% end %}
end

DEFAULT_PATH = {{platform_specific_path}}
puts DEFAULT_PATH

功能标志

macro with_feature(flag, &block)
  {% if flag?(flag) %}
    {{yield}}
  {% end %}
end

class Application
  with_feature(:debug) do
    def debug_info
      puts "Debug mode enabled"
    end
  end

  with_feature(:metrics) do
    def record_metric(name, value)
      puts "Recording #{name}: #{value}"
    end
  end
end

# 使用编译:crystal build app.cr -Ddebug -Dmetrics

基于类型的条件

macro generate_serializer(type)
  {% if type.resolve < Number %}
    def serialize_{{type.name.downcase.id}}(value : {{type}}) : String
      value.to_s
    end
  {% elsif type.resolve == String %}
    def serialize_{{type.name.downcase.id}}(value : {{type}}) : String
      value.inspect
    end
  {% elsif type.resolve < Array %}
    def serialize_{{type.name.downcase.id}}(value : {{type}}) : String
      "[" + value.map(&.to_s).join(", ") + "]"
    end
  {% end %}
end

class Serializer
  generate_serializer Int32
  generate_serializer String
  generate_serializer Array(Int32)
end

s = Serializer.new
puts s.serialize_int32(42)
puts s.serialize_string("hello")
puts s.serialize_array_int32([1, 2, 3])

AST 节点类型

宏接收不同类型的AST节点。理解这些至关重要。

检查AST节点

macro show_ast(expression)
  {{expression.class_name}}
end

# NumberLiteral
puts {{show_ast(42)}}

# StringLiteral
puts {{show_ast("hello")}}

# Call
puts {{show_ast(foo.bar)}}

# ArrayLiteral
puts {{show_ast([1, 2, 3])}}

使用标识符

macro create_accessor(name)
  # name 是 SymbolLiteral 或 StringLiteral
  # 使用 .id 转换为标识符
  def {{name.id}}
    @{{name.id}}
  end

  def {{name.id}}=(value)
    @{{name.id}} = value
  end
end

class User
  def initialize
    @username = ""
  end

  create_accessor :username
end

操作字符串字面量

macro define_constants_from_string(str)
  {% parts = str.split(",") %}
  {% for part in parts %}
    {{part.strip.upcase.id}} = {{part.strip.id.stringify}}
  {% end %}
end

module Colors
  define_constants_from_string("red, green, blue, yellow")
end

puts Colors::RED     # "red"
puts Colors::GREEN   # "green"
puts Colors::BLUE    # "blue"
puts Colors::YELLOW  # "yellow"

高级宏模式

构建路由DSL

macro route(method, path, handler)
  {% ROUTES ||= [] of {String, String, String} %}
  {% ROUTES << {method.stringify, path, handler.stringify} %}
end

macro compile_routes
  ROUTES_MAP = {
    {% for route in ROUTES %}
      {{route[1]}} => {{route[2].id}},
    {% end %}
  }

  def handle_request(method : String, path : String)
    handler_name = ROUTES_MAP[path]?
    return not_found unless handler_name

    case handler_name
    {% for route in ROUTES %}
    when {{route[2]}}
      {{route[2].id}}
    {% end %}
    end
  end
end

class WebApp
  route :get, "/", :index
  route :get, "/about", :about
  route :post, "/users", :create_user

  def index
    "Home Page"
  end

  def about
    "About Page"
  end

  def create_user
    "Create User"
  end

  def not_found
    "404 Not Found"
  end

  compile_routes
end

自动JSON序列化

macro json_serializable(*fields)
  def to_json(builder : JSON::Builder)
    builder.object do
      {% for field in fields %}
        builder.field {{field.stringify}} do
          @{{field.id}}.to_json(builder)
        end
      {% end %}
    end
  end

  def self.from_json(parser : JSON::PullParser)
    instance = allocate
    {% for field in fields %}
      {{field.id}} = nil
    {% end %}

    parser.read_object do |key|
      case key
      {% for field in fields %}
      when {{field.stringify}}
        {{field.id}} = typeof(instance.@{{field.id}}).from_json(parser)
      {% end %}
      end
    end

    {% for field in fields %}
      instance.@{{field.id}} = {{field.id}}.not_nil!
    {% end %}

    instance
  end
end

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

  json_serializable name, age, email
end

user = User.new("Alice", 30, "alice@example.com")
json = user.to_json
puts json

编译时配置

macro configure(&block)
  {% begin %}
    {% config = {} of String => ASTNode %}
    {{yield}}
    {% for key, value in config %}
      {{key.upcase.id}} = {{value}}
    {% end %}
  {% end %}
end

macro set(key, value)
  {% config[key.stringify] = value %}
end

configure do
  set :app_name, "MyApp"
  set :version, "1.0.0"
  set :max_connections, 100
  set :debug, true
end

puts APP_NAME           # "MyApp"
puts VERSION            # "1.0.0"
puts MAX_CONNECTIONS    # 100
puts DEBUG              # true

宏方法

宏方法在类型上调用,可以访问编译时类型信息。

从类型信息生成方法

class Model
  macro inherited
    # 当类继承自 Model 时调用
    def self.table_name : String
      {{@type.name.underscore.id.stringify}}
    end

    def self.column_names : Array(String)
      [
        {% for ivar in @type.instance_vars %}
          {{ivar.name.stringify}},
        {% end %}
      ]
    end
  end
end

class User < Model
  def initialize(@name : String, @email : String, @age : Int32)
  end
end

puts User.table_name       # "user"
puts User.column_names     # ["name", "email", "age"]

属性自省

class Base
  macro generate_initializer
    def initialize(
      {% for ivar in @type.instance_vars %}
        @{{ivar.name}} : {{ivar.type}},
      {% end %}
    )
    end

    def to_s(io : IO)
      io << "{{@type.name}}("
      {% for ivar, index in @type.instance_vars %}
        {% if index > 0 %}
          io << ", "
        {% end %}
        io << "{{ivar.name}}="
        @{{ivar.name}}.inspect(io)
      {% end %}
      io << ")"
    end
  end
end

class Person < Base
  @name : String
  @age : Int32
  @city : String

  generate_initializer
end

person = Person.new("Bob", 25, "NYC")
puts person  # Person(name="Bob", age=25, city="NYC")

方法委托

macro delegate(*methods, to target)
  {% for method in methods %}
    def {{method.id}}(*args, **kwargs)
      @{{target.id}}.{{method.id}}(*args, **kwargs)
    end

    def {{method.id}}(*args, **kwargs, &block)
      @{{target.id}}.{{method.id}}(*args, **kwargs) { |*yield_args| yield *yield_args }
    end
  {% end %}
end

class UserRepository
  def find(id : Int32)
    "User #{id}"
  end

  def all
    ["User 1", "User 2"]
  end

  def create(name : String)
    "Created #{name}"
  end
end

class UserService
  def initialize
    @repository = UserRepository.new
  end

  delegate find, all, create, to: repository
end

service = UserService.new
puts service.find(1)
puts service.all

调试宏

编译时打印

macro debug_print(value)
  {{puts value}}
  {{value}}
end

# 这会在编译时打印
result = {{debug_print(42 + 8)}}

# 在编译时打印类型信息
macro show_type_info(type)
  {% puts "Type: #{type.resolve}" %}
  {% puts "Instance vars: #{type.resolve.instance_vars.map(&.name)}" %}
  {% puts "Methods: #{type.resolve.methods.map(&.name)}" %}
end

class Example
  @x : Int32 = 0
  @y : String = ""

  def foo
  end

  def bar
  end
end

{{show_type_info(Example)}}

宏扩展检查

# 使用 --no-codegen 标志查看宏扩展
# crystal build --no-codegen app.cr

macro verbose_property(name, type)
  {{puts "Generating property #{name} of type #{type}"}}

  @{{name}} : {{type}}?

  def {{name}} : {{type}}?
    {{puts "Generating getter for #{name}"}}
    @{{name}}
  end

  def {{name}}=(value : {{type}})
    {{puts "Generating setter for #{name}"}}
    @{{name}} = value
  end
end

class Config
  verbose_property timeout, Int32
  verbose_property host, String
end

何时使用此技能

在需要时使用crystal-macros技能:

  • 通过代码生成减少样板代码
  • 为配置或业务逻辑构建领域特定语言(DSL)
  • 生成重复的方法、类或模块
  • 实现编译时验证和类型检查
  • 创建具有自定义行为的属性定义
  • 生成序列化/反序列化代码
  • 构建基于注解的编程模式
  • 实现自动委托或代理
  • 创建编译时配置系统
  • 从模式定义生成数据库模型
  • 构建具有自定义断言的测试框架
  • 实现编译时依赖注入
  • 创建类型安全的构建器模式
  • 从规范生成API客户端
  • 实现面向切面编程模式

最佳实践

  1. 保持宏简单:将复杂宏分解为更小、可组合的部分
  2. 记录宏行为:解释宏生成什么代码以及原因
  3. 使用有意义的名称:宏名称应清晰指示其生成内容
  4. 验证输入:尽可能在编译时检查宏参数
  5. 优先使用宏方法:对类型特定逻辑使用宏方法而非顶层宏
  6. 使用 {{yield}}:将块传递给宏以实现灵活的代码生成
  7. {{puts}} 调试:在宏开发期间打印AST节点和值
  8. 测试生成代码:验证宏生成的代码按预期工作
  9. 避免过度使用:仅当益处超过复杂性时使用宏
  10. 使用类型信息:利用 @type 和反射实现强大抽象
  11. 处理边缘情况:考虑nil值、空集合和类型变化
  12. 保持可读性:生成代码应与手写代码一样可读
  13. 谨慎版本控制:宏更改可能破坏下游代码;适当版本化
  14. 使用条件编译:利用标志进行平台特定或功能特定代码
  15. 记录扩展:在宏文档中展示扩展代码示例

常见陷阱

  1. 忘记 .id 转换:字面量必须使用 .id 转换为标识符
  2. 字符串与符号混淆:知道何时使用 stringify 与字面量插值
  3. 无限宏递归:递归宏必须具有适当的终止条件
  4. 作用域问题:宏作用域中的变量与生成代码作用域可能冲突
  5. 类型解析时机:某些类型信息在早期编译时不可用
  6. 缺少Nil检查:生成代码可能未正确处理nil
  7. 硬编码假设:宏假设特定类型结构可能发生变化
  8. 错误消息不佳:生成代码中的编译错误难以调试
  9. 过度使用全局状态:宏中的类变量可能导致意外行为
  10. 未处理空集合:迭代空数组/哈希而未检查
  11. 模板中的语法错误:宏主体中的无效Crystal语法导致混淆错误
  12. 类型不匹配:生成代码不匹配预期类型
  13. 命名空间污染:在全局作用域中生成过多方法或常量
  14. 平台依赖:未处理宏逻辑中的平台差异
  15. 循环依赖:宏依赖于依赖于相同宏的类型

资源