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