dogweather

dogweather

Defining a new (custom) type - best practices? Cleaner code?

Here’s a simple type for a piece of historical stock info:

defmodule DayRecord do
  @enforce_keys [:date, :close, :high, :low, :open]
  defstruct date: ~D[1900-01-01], close: 0, high: 0, low: 0, open: 0
  @type t :: %__MODULE__{date: Date.t, close: Float, high: Float, low: Float, open: Float}
end

I’m sure there’s more I can add, but I’m already a little unhappy with the redundancy and mix of different DSLs and implementation details.

Am I going down the right path?

I’ve seen a Hex package that wraps some of this up. Is there any traction around a certain way of removing the boilerplate? Maybe continuing defstruct’s example, and making a higher level macro?

Most Liked

al2o3cr

al2o3cr

First thought: enforce_keys and default values are at cross-purposes; the defaults supply a value for when the key isn’t passed to %{}, but enforce_keys requires that the key be passed. Note that enforce_keys is NOT a “not null” constraint - it’s completely valid to pass nil for an enforced key.

If you have some fields that should always be passed but others that should be defaulted, you could extract some of the duplication:

defmodule StructDemo do
  @enforce_keys [:first_name, :last_name]
  @optional_keys [age: 0, heads: 1]
  defstruct @enforce_keys ++ @optional_keys
end

Usage:

iex(2)> %StructDemo{first_name: "Bob", last_name: "Dobbs"}
%StructDemo{age: 0, first_name: "Bob", heads: 1, last_name: "Dobbs"}

iex(3)> %StructDemo{first_name: "Prince"}
** (ArgumentError) the following keys must also be given when building struct StructDemo: [:last_name]
    expanding struct: StructDemo.__struct__/1
    iex:3: (file)

iex(3)> %StructDemo{first_name: "Nobody", last_name: nil}
%StructDemo{age: 0, first_name: "Nobody", heads: 1, last_name: nil}
ZsoltMaslanyi

ZsoltMaslanyi

Not OP, but that’s straight up beautiful. Thank you!

Adzz

Adzz

An alternative you can consider also is using Ecto (note you don’t need ecto_sql here either). In that case you can create an embedded schema like so:

defmodule DayRecord do
  use Ecto.Schema

  embedded_schema do
    field(:date, :date)
    field(:close, :decimal)
    field(:high, :decimal)
    field(:low, :decimal)
    field(:open, :decimal)
  end
end

As already mention enforcing the keys then adding defaults is a bit redundant, but if you want to add defaults you can in Ecto too:

defmodule DayRecord do
  use Ecto.Schema

  embedded_schema do
    field(:date, :date)
    field(:close, :decimal)
    field(:high, :decimal)
    field(:low, :decimal)
    field(:open, :decimal, default: Decimal.new(0))
  end
end

To me this makes it perfectly clear the types of each field. Then benefit then is you can use changesets and validations on creation to enforce things like non_null fields etc.

I’ve also been working on a library to give some sugar to this kind of use case called ecto_morph

Where Next?

Popular in Discussions Top

Rustixir
Hi everyone, im working on find best language/framework/system for high concurrency, high performance and stable performance after wor...
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
New
mmport80
I have put far too much effort into Dialyzer over the last year or so - and basically - I doubt it’s worth the effort. It’s not as easy ...
New
lorenzo
Hey everone! I created a prototype for my app using Nodejs for the api. But the framework I chose wasnt great (in general theresnt any g...
New
CharlesO
Erlang :list.nth simple, but 1 - based nth(1, [H|_]) -> H; nth(N, [_|T]) when N > 1 -> nth(N - 1, T). Elixir Enum.at … coo...
New
ejpcmac
I have discovered Nix last month and I am currently on my way to migrating to it—both on macOS at home and the full NixOS distrubution at...
New
fireproofsocks
I’ve been working on an Elixir project that has required a lot of scripting. I usually reach for Elixir because I like it more (and in th...
New
Qqwy
I would like to spark a discussion about the static access operator: .. For whom does not know: it is used in Elixir to access fields of...
New
paulanthonywilson
I like Umbrella projects and pretty much always use them for personal Elixir stuff, especially Nerves things. But I don’t think this is ...
New

Other popular topics Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29377 241
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New

We're in Beta

About us Mission Statement