Fl4m3Ph03n1x

Fl4m3Ph03n1x

Background

I am trying to find a cheap and easy way to create New Types in Elixir, and Records seem to be just what I would need.

Problem

However, Elixir records require one to define default values. Not only that, it also allows one to create empty records (which would then be populated with said default values).

For my specific use case, this is a problem. Not only don’t I have anything that can be used as a default value, I also don’t want to allow the users of my code to create empty records.

Now, I understand this is likely a well intended choice, most likely so it can interface nicely with Erlang records, but it causes an usability issue on my end: it allows the creation of non valid data.

Questions

I understand there is probably no solution for this conundrum using Records only, so I was wondering if there are alternatives in the wild of libraries or even hacks to accomplish this.

I personally have found nothing, right now I have the feeling my only solution is to write my own macro.

  • Is there a way to have Records not accept default values?
  • If not, what community libraries are out there that could help fulfill the role of creating a New Type?

Showing Posts 1 to 4

LostKobrakai

LostKobrakai

In my opinion focusing this on records is a red herring. In neither elixir nor erlang can you prevent non-native “datatypes” from being created without custom validation. You can always manually construct the lower level data directly used to represent higher level data. This applies to both records (custom user land types common in erlang) as well as structs (custom user land types common in elixir), but likely also any other option you could come up with using the native types we have. If there is a way I’d expect it to come with other downsides, like e.g. considerable runtime overhead.

Comparing this to approaches in statically typed languages (as you’ve been expressing in prev. posts) is imo not a great idea given their type system and specifically it being static is what makes many of those approaches possible/viable in the first place.

To be a bit more concrete in regards to records: Records are just a convention around tuples (first elem is an atom denoting the type) just like structs are a convention around maps (__struct__ key + small api defined by the struct modules). You can construct a user record manually by doing record = {:user, name} similarly to constructing a struct manually. Neither of both approaches is meant to prevent people from doing that (given it’s effectivly impossible to enforce).

So what are the alternatives? None if it’s about the compiler / code level enforced validation. But you can still have project level conventions of having your team call MyType.new(…) as the only valid way of constructing a type picking up violations e.g. in code reviews.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

Thank you for your input!

So in your opinion, conventions are the key here, while trying to force this on Records would be a mistake. I see!

I would like to stay away from statically typed languages in this specific discussion (well, for as long as it is possible, what I mean is I dont want to turn this into a static VS dynamic type of thread).

I like to think about going more on the way of: “How can I help dialyzer easily find more errors?” NewType is a way of trying to deal with a specific class of bugs that dialyzer can’t detect. If there is another way that does not involve NewType, I am all ears.

I also understand that no matter what Macro I use, the user can always deep into the code and just build the data structures directly, which would be bad. So validation comes in to prevent that.

Interesting piece, I am happy you shared it !

LostKobrakai

LostKobrakai

You can take a look at MapSet. It deals with all of the questions you had. It’s not a native type, but is meant to maintain “set” properties. It does so by marking the internals as @opaque t::… to tell dialyzer nobody but the module itself may update values returned by the API and then basically expecting people to only supply valid mapsets for all the API receiving existing sets.

Edit: Also dialyzer will yell at you trying to modify internals of an opaque type return value. Doesn’t prevent the “created manually” case though afaik.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

Answer

  1. Is there a way to have Records not accept default values?

No. This is not possible with Records. Records were never intended for this use case and forcing this abstraction into them would only complicate things. While one could use a wrapper new method, it would still be a lot of boilerplate and all the validation for type would be on the user.

  1. At the time of this writing, there are none. However, in another post I created a macro that achieves this purpose: How to define Macro for a new Type? - #10 by Fl4m3Ph03n1x

In that post I propose an API and then I refine it with the community’s help. For those of you who are curious, it can be used like this:

type.ex

defmodule Type do
  import NewType

  deftype(Name, String.t())
end

test.ex

defmodule Test do
  alias Type.Name

  @spec print(Name.t()) :: binary
  def print(name), do: Name.extract(name)

  def run_1 do
    # dialyzer complains !
    Name.new(1)
  end

  def run_2 do
    # dialyzer complains !
    print("john")
  end

  @spec run_3 :: binary
  def run_3 do
    print(Name.new("dow"))
  end
end
— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

ElixirConf
Failing to Introduce Elixir - John Darrington https://www.youtube.com/watch?v=KjAH68yVnh8 Comments welcome! View the <span class="hasht...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews