Design Help for a Noob

Hi everyone. I’m a (very) new elixir programmer but I’m really enjoying it so far. I have a question on idiomatic Elixir. If this isn’t the right place to post this question, sorry in advance!

I’m cutting my teeth with a basic “Store” application (think grocery store). It has an inventory of items, and clients can connect to the store and buy them, adding them to their inventories. Super simple.

I’m struggling with how best to represent “Currency” or “Value”. Items in the store have value, and clients have money to buy those items. I want to use gold, silver and copper coins. In an OOP language I would make them objects and maybe subclass coin. In OCaml/Haskell I would probably use a variant type:

type coin = Gold of int | Silver of int | Copper of int

My current thinking is to make a “Value” module with a struct representing the total value, and functions to manipulate it. Then each item in the store has a Map with its info, including a Value field, and each Client has an Inventory with an associated Value representing their total money.

defmodule Value do
  defstruct(
    :gold => 0
    :silver => 0
    :copper => 0
  )

  def add(value, gold, silver, copper) ....
  def add(value1 = %Value{}, value2 = %Value{}) ....
  def subtract(value, gold, silver, copper) ....
  def subtract(value1 = %Value{}, value2 = %Value{}) ....

end

This feels sort of OOP to me. Does this make sense? Is there a more idiomatic way to do this in Elixir? Any feedback is greatly appreciated. Thanks!

1 Like

I’m not that much into OCaml, but this is far from equivalent to your coin-type you specified using OCaml syntax.

It is much more like type coin = { gold : int; silver : int; copper : int; }.

If you want something equivalent to the previously mentioned sum-type, you could use a three element tagged tuple:

{Value, amount, coin_type}

Whereas amount should be an integer and coin_type is one of :gold, :silver, :copper.

4 Likes

What @Nobbz said, if you wanted the OCaml form in Elixir you’d make them as:

{:Gold, value}
{:Silver, value}
{:Copper, value}

Although I’d bet you would actually prefer the record form that you did in Elixir so you can mix them together. Although optimally you would reduce it down to a single integral value so you could take mixtures of the gold/silver/copper rather than only specific amounts.

4 Likes

I’m not sure if using a single integral value is sufficient.

Lets consider a situation where one has 100 cents. Even though the value is equivalent to a dollar, the person working at wallmarts might deny to accept them.

So in fact, I think the struct were the most appropriate thing to use.

1 Like

It would true, but I’m thinking more of the realm of games or so (considering copper/silver/gold). ^.^

If real world money is used then definitely absolutely do not write it yourself, use ex_money or so.

Depends on your definition of OOP but in my eyes, it isn’t – OOP would be if you have an object plus methods encapsulated, while in FP languages you have a module that works with any instance of the structure (which is incidentally defined in the same module; that link isn’t a given, it’s just a convenient default which FP programmers use all the time) and the module’s state itself isn’t affected in any way.

Only thing I’d change is to make as detailed typespecs as possible – dialyzer isn’t perfect but it can help you catch a class of logical problems early.

I think you did quite well.

1 Like

Thanks – perhaps my OCaml example wasn’t the best. Or maybe I’m just missing sum types :slight_smile:

I hadn’t thought of a tagged tuple. In the example you give, it seems like Value is the name of a module, but not a struct of that module. So – I’d use the name of a module as the tag for my tuple? Is this a normal pattern in elixir?

Thanks! I have two questions here:

  1. The example with {:Gold, value} etc doesn’t specify a set of possible variants. So it’s possible a {:Platinum, value} might be passed to a function expecting only :Gold, :Silver, :Copper. Would the idea be that those functions pattern match on the three possibilities to “enforce” the “type” of the coin?

  2. For a single integral value, would it make sense to use rem/2 and div/2 to pull it apart? So copper is rem(value, 1000), silver is rem(div(value,1000),1000) and gold is value/1000000? (Assuming 1000 copper to a silver), and then I just pass around an integer?

The idiom (since the erlang days) is to use an atom that uniquely identifies the record. In most cases the atom is the modulename that implements most functions around the “datatype”.

For modules that implement many of those, often a suffix is used.

1 Like

Gotcha. This is super helpful. Thanks again for your help.

Precisely. Elixir is not strongly typed so you enforce constrains by matching and guards at runtime instead. :slight_smile:

That’s what I’d for a simple little game coinage system, although I personally like nice round numbers like 1024 or 8192 or so (base 10 is so weird to do math in to me, I grew up with powers of 2… though personally I think base 6 is awesome but nothing uses it). ^.^;

1 Like

Makes sense. Thanks very much.