Is pattern matching to enforce structure considered a good "style"?

Was looking at https://github.com/mrwade/multitron/blob/master/lib/multitron/game.ex

 def add_player(game, player_id, {_name, _color, _x, _y, _direction} = player) do
      %{game | players: Map.put(game.players, player_id, player)}
  end
3 Likes

I do all the time, along with probably excessive use of when and plenty of @specs. Although for that game specific case Iā€™d probably actually use a record instead of a tuple (basically the same thing, but one has names), or a struct (touch slower, but easier).

4 Likes

I agree. If you were using a struct it would just look like

def add_player(game, player_id, %Player{} = player) do
  %{game | players: Map.put(game.players, player_id, player)}
end
6 Likes

Thanx guys :slight_smile: Itā€™s not my code I was looking at the talk on youtube and this looked a bit wiered to me so wanted to check with more experienced elixir devs if this is considered a good practice or not.

3 Likes

Seeing your topic title SaÅ”a Jurićā€™s Elixir in Action immediately came to mind - pp. 110-111:

4.1.4 Abstracting with structs
ā€¦
The benefit of pattern matching is that input type is enforced. If you pass anything that isnā€™t a fraction instance, youā€™ll get a match error.
ā€¦
By representing fractions with a struct, you can provide the definition of your type, listing all fields and their default values. Furthermore, itā€™s possible to distinguish struct instances from any other data type. This allows you to place %Fraction{} matches in function arguments, thus asserting that you only accept fraction instances.

Only upon reading your post did it become apparent that in this case the ā€œstructureā€ was a mere tuple.

4 Likes

Yep best book Iā€™ve read on Elixir coming from OOP background I am afraid I might have a tendency to overuse structs :slight_smile:

1 Like

I think the best approach is to only match on the structure of something if it matters for that specific function. In some of the earlier code I wrote, I often wrote out some or all fields of a struct I was working on, without these fields actually all being used inside the function. This made it very hard to change the struct around later.

On the other hand I do like to check if my structs have a :fieldname if I use (structname.fieldname) inside the function, because in this case the structure is important.

2 Likes

Having done most OOP I think I might have a tendency to over use structs, but how about these guide lines?

  1. In a struct module %Fubar, use def do_fu_stuff(%Fubar{} = fb) for functions that operate on the self type. Use further matching if you need some specific field.

  2. In another module Bar that could takes %Fubar as arguments, use def def do_bar_stuff(%{f: f, u: u} = fb).

This way you get ā€˜strictā€™ local type checking without creating unnecessary coupling to the type from another module that could possibly be useful with some other type.

Like this:

defmodule Fubar do
  defstruct f: nil, u: nil
  def do_fu_stuff(%Fubar{} = fb), do: nil
end

defmodule Bar do
  def do_bar_stuff(%{f: f, u: u} = fb), do: nil
end
3 Likes

I think that would be fine, except that I wouldnā€™t worry too much about the unnecessary coupling and just do this the external module:

defmodule Bar do
  def do_bar_stuff(%Fubar{f: f, u: u} = fb), do: nil
end

If Bar is a module that provides generic functionality for possibly many data types, I would just define a protocol instead and implement this protocol for the data types that might benefit from Bar's functionality. That way you have even less coupling, as every type can define/name the fields it needs, without worrying about the field names Bar expects.

-vincent

4 Likes