[Proposal] defrecord syntactic sugar like defstruct

40 views
Skip to first unread message

Kevin Johnson

unread,
Jan 3, 2020, 2:07:43 AM1/3/20
to elixir-lang-core
First proposal for 2020!

Just like a `struct` is an extension over a `map` with its own dedicated syntactic sugar, what prevents to regard a `record` to be an extension over a `tuple` with syntactic sugar en par with that of a `struct`?

When we introduce a `struct`:

defmodule A do
 defstruct a: 1
end

Instead of interacting with it like: 
%{__struct__: A, a: i} = a

We can interact with it as:

%A{a: i}= a

and likewise we can pattern match the same on function arguments:

def set(m =%A{},i), do:%{m|a: i}


According to the documentation a variety of `macro`s have already been introduced to abstract away from the underlying `tuple` as a data structure. 

I am wondering if there would be any merit if a `Record` could be accorded with its own special notation that would allow us to pattern match accordingly?

Currently the equivalent of the `set`-`method` above is to be written using a `guard`, e.g.:
defmodule B do
 import Record
 defrecord(:bb, a: 1)

  def set(record, i) when is_record(record, :bb) do
    bb(record, a: i)
 end
end


and pattern matching on a specific value can only be done by resorting to the underlying tuple implementation:

require B
{:bb, i}= B.bb()


It would be nice if we could introduce a special notation such as:
~bb{a: 1}

So then we could:
def set(record = ~bb{}, i) do

and:
require B
~B.bb{a: i} = B.bb()



These two aspects are the core aspects of my proposal. In addition to this, I would like to highlight that it would be helpful if we could bridge the gap even more by being able to do:
defmodule B do
import Record
defrecord(__MODULE__, a: 1)

def set(record = ~__MODULE__{}, i) do
...
which in itself is possible, but at the expense of losing access to the underlying macros as enumerated in the documentation.

You would not be able to do:
require B
# B()
# B.B()

Also it would be nice to be able to update similar to a map:
defmodule B do
import Record
defrecord(__MODULE__, a: 1)

def set(record = ~__MODULE__{}, i) do
~__MODULE__{record | a: i}
end
end


These last two suggestions are mainly to illustrate how the end result could potentially look like if we were to fully bridge the gap so to say, but the most essential one is the pattern matching aspect detailed above.

Alexei Sholik

unread,
Jan 3, 2020, 4:20:13 AM1/3/20
to elixir-lang-core
It's already possible to match on record's individual fields, you just need to import it:

iex(2)> import B
B
iex(3)> bb(a: i) = bb()
{:bb, 1}
iex(4)> i
1


--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/36e4c070-4efc-4641-8341-f3a95c71a905%40googlegroups.com.


--
Best regards
Alexei Sholik

Alexei Sholik

unread,
Jan 3, 2020, 4:22:27 AM1/3/20
to elixir-lang-core
And it's also possible to update selected fields of an existing record. This is all described in the docs:

iex(5)> r = bb()
{:bb, 1}
iex(6)> r1 = bb(r, a: 2)
{:bb, 2}
iex(7)> bb(r1, :a)
2
iex(8)> bb(a: x) = r1
{:bb, 2}
iex(9)> x
2

Kevin Johnson

unread,
Jan 3, 2020, 5:41:32 PM1/3/20
to elixir-l...@googlegroups.com
Thank you Alexei for clarifying.

I did not see that you could use the macro in a pattern match like that. The following is also possible if you want to avoid using a guard:

defmodule B do
  import Record
  defrecord(:bb, a: 1)

  def set(record = bb(), i) do
    bb(record, a: i)
  end
end

The `macro` does provide all the convenience you need.

The only matters outstanding are:
  1. Usage of  `__MODULE__` as the `record` `atom`(without losing the macro convenience)
  2. Having the syntax resemble that of defstruct more notation wise, e.g. `~B{a: i}` including in terms of updating
Those would make it easier to pick up `defrecord` with all its nuances but are certainly not critical. 

`defrecord` functionality wise is feature complete so I withdraw my proposal.

Allen Madsen

unread,
Jan 4, 2020, 9:47:12 PM1/4/20
to elixir-l...@googlegroups.com
Tilde (~) is already used for sigils, so it can't be used for this.

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages