Search code examples
macroserlangrecordquoteelixir

Elixir quote record(Turn it into a tuple) and preserve data?


When quoted using quote do: records aren't converted to tuples containing the record fields:

 iex(1)> quote do: is_bitstring("blah")
 {:is_bitstring, [context: Elixir, import: Kernel], ["blah"]}
 iex(2)> quote do: Computer.new("Test")
 {{:., [], [{:__aliases__, [alias: false], [:Computer]}, :new]}, [], [[name: "Test"]]}
 iex(3)> quote do: Computer.new("Test")
 {{:., [], [{:__aliases__, [alias: false], [:Computer]}, :new]}, [], [[name: "Test"]]}
 iex(4)> c = Computer.new("Test")
 Computer[name: "Test", type: nil, processor: nil, hard_drives: []]
 iex(5)> c
 Computer[name: "Test", type: nil, processor: nil, hard_drives: []]
 iex(6)> quote do: c
 {:c, [], Elixir}

Also, when I try doing this in my code:

defmacro computer([do: code]) do
  # macro login here
  # build computer record based on macro logic
  computer = Computer.new(params)
  quote do: unquote computer
end

I get an error:

** (CompileError) elixir/test/lib/computer_dsl_test.exs: tuples in quoted expressions must have 2 or 3 items, invalid quoted expression: Computer[name: "", type: nil, processor: nil, hard_drives: []]

I thought that records were just tuples with wrappers functions of some sort. The Elixir Getting Started guide states "A record is simply a tuple where the first element is the record module name." Is there something I am missing? Is there a function I can call on a record to get the tuple representation? I am aware of the raw: true option but I am not sure how to use that on an existing record.

Any insights?


Solution

  • Records are tuples. The output you see on the console is just formatted for easier inspection. You can check that records are tuples if you inspect them with raw: true:

    iex(1)> defrecord X, a: 1, b: 2
    
    iex(2)> x = X.new
    X[a: 1, b: 2]   # This is formatted output. x is really a tuple
    
    iex(3)> IO.inspect x, raw: true
    {X, 1, 2}
    

    As can be seen, a record instance is really a tuple. You can also pattern match on it (although I don't recommend this):

    iex(4)> {a, b, c} = x
    
    iex(8)> a
    X
    
    iex(9)> b
    1
    
    iex(10)> c
    2
    

    The quote you are mentioning serves completely different purpose. It turns an Elixir expression into AST representation that can be injected into the rest of the AST, most often from the macro. Quote is relevant only in compile time, and as such, it can't even know what is in your variable. So when you say:

    quote do: Computer.new("Test")
    

    The result you get is AST representation of the call of the Computer.new function. But the function is not called at this point.