Fl4m3Ph03n1x
Background
So, I am playing around with a concept named “NewType” and I am taking inspiration from languages like F# and Scala.
My objective, for learning purposes mostly, is to build a macro that makes creating this abstraction something that takes no more than a single line of code.
Intended usage
I would like to create a macro that allows me to do something like this:
defmodule User do
require NewType # an absolutely original name for the macro :D
deftype Name, String.t() # Usage of said macro. Here I am defining a new type called "Name"
@enforce_keys [:name, :age]
defstruct [:name, :age]
@type t :: %__MODULE__{
name: Name.t,
age: integer()
}
@spec new(Name.t, integer) :: User.t
def new(name, age), do: %User{name: name, age, age}
end
And now, here is how I could create a User:
defmodule Test do
alias User
import User.Name
@spec run :: User.t
def run do
name = Name("John")
User.new(name, 25)
end
end
How to implement this interface?
This interface might remind you a little of the Record interface. That’s because I think its API has some good ideas I would like to explore.
So, as a starting point I tried reading the source code for Record, but I was not really able to pick it up and use it to create an implementation for my use case, mainly because I don’t need/want to interface with Erlang records at all.
So, an implementation possibility would be to, under the hood, turn this into a tuple:
defmodule NewType do
defmacro new(name, val) do
quote do
NewType.to_tuple(unquote(name), unquote(val))
end
end
def to_tuple(name, val), do: {String.to_atom(name), val}
end
However, this is miles away from the interface I want to create …
Questions
- Using Elixir macros, is it possible to create the API I am aiming for?
- How can I change my code to achieve something like
Name("John")?
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #blog-post
- #ai
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 9- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
LostKobrakai
That’s not possible with the uppercase first letter. Any call to a macro or function needs to start with a non uppercase letter.
Syntax reference
Fl4m3Ph03n1x
Can’t a Macro generate a Module’s Name dynamically?
Let’s say, if I have a struct, couldn’t a Macro
deftype, given an Uppercase name, dynamically generate this struct for me?LostKobrakai
You can generate modules, but you cannot “call” modules. You can only call macros or functions. So
Name("…")is not valid elixir syntax –Name.new("…")is.Fl4m3Ph03n1x
Right!
A revision of my basic idea is needed !
This should possible, right ?
lud
Yes.
Start by writing the
Namemodule by hand. That will show you what your macro would have to generate.Fl4m3Ph03n1x
Here, I have defined a couple of hypothetical types,
NameandAge. Their respective modules would be:name
which would be generated by invoking:
age
which would be generated by using:
Do note a few things:
Would it be possible to achieve this, using macros?
If so, how?
benwilson512
Probably. Mutually recursive types however may well be difficult to do with this.
A lot of macro work. I’d start with the Metaprogramming Elixir book.
As an aside though: I’d seriously consider looking at Gleam. It’s a BEAM language w/ static types. If you want to push forward the boundaries of what will be possible with types on the BEAM I imagine that work in that ecosystem is going to be more fruitful than attempting to make it work in Elixir. If Jose tried to make it work and failed, I would think long and hard about taking it on yourself.
Fl4m3Ph03n1x
Can you elaborate more on this?
I was hoping there was a quicker way to achieve this goal, one that does not involve reading a full book. My read list is already big enough
I absolutely like the idea of Gleam, however the ecosystem is not there yet. You might say that Gleam is compatible with Elixir and Erlang libraries, but then if half of my project is in Gleam and the other half in Elixir, I just don’t see how that can end up well.
I don’t compare myself with people in general, but I was always under the impression that Jose never intended Elixir to be statically typed, or to even have many of the features static type languages have (please correct me if I am wrong).
While I am not trying to one up anyone, I am also not trying to re-invent the wheel. I am, from my humble point of view, trying to bring a concept generalized and well known in many other functional languages into Elixir, adapting it to what some could consider to be “in an Elixir way”.
At the end, there is a good chance you might be correct, and that I am in a fool’s errand. However I will never find out if I don’t try.
Fl4m3Ph03n1x
My Answer
After reading more about macros in Elixir, talking to the community and reading about
NewType, I have refined my ideas. While the exact implementation of my original idea is not possible, with some changes you can still get the core benefit ofNewType.Changes to original idea
Name("John")syntax. As explained in this post this syntax is not valid in Elixir.defguard. Because the type is@opaqueit is not possible to have a guard that analyses the internal structure of the data without having dialyzer complaining. Since the main goal here is to have Dialyzer help me detect issues, and since the internal structure of the opaque data can only be analyzed by functions that belong to the module itself, this means this idea is not possible.new. Originally I thought about having some verification mechanism, but this is not necessary, since dialyzer will let the user know if the user is invokingnewwith an incorrect parameter.Age.age?orName.name?I have opted for the more generalNewType.is_type?/2, which will accomplish the same and is more general.Code
With these changes in mind, this is the macro I came up with:
Which can be used like:
type.ex:
test.ex: