Is there a shorthand version to destructure maps/structs in function definition

Sorry that you think that I’m derailing. Past anecdotes are essential for demonstrating why certain practices are [not] good or, as in this case, why certain disclaimers are mandatory and shouldn’t be implied.

I’m 100% okay with not sharing anecdotes, sure, but in my eyes that would make certain parts of the discussion purely theoretical, which I view as much less valuable. In which case I’ll choose to not participate.

Well your dot function is using pattern matching while your match function is using the . operator which is confusing! Probably a mistake or we are maybe not talking about the same things.

I ran a slightly different benchmark and the results are non significant. thank you because I’ll use . a lot more from now on as we took the time to test that.

Here is the benckmark

inputs = %{
  "large" => Map.new(1..33, fn n -> {:"key_#{n}", []} end),
  "single" => %{key_1: []}
}

Benchee.run(
  %{
    "body_match" => fn map ->
      %{key_1: value} = map
      value
    end,
    "case" => fn map ->
      case map do
        %{key_1: value} -> value
      end
    end,
    "dot" => fn map ->
      map.key_1
    end,
    "head_match" => fn
      %{key_1: value} ->
        value
    end
  },
  inputs: inputs,
  pre_check: true,
  time: 1
)

Had to set time: 1 or otherwise my RAM is entirely taken after the last scenario/input runs. No matter which one. Also it does not max out my CPU.

So I ran this instead, which has more overhead but keeps your technique of looping inside the scenarios so there are less results to compute (I guess that is what makes benchee eat all the ram) but not building lists (as in for expressions) to save RAM. But I get way more consistent results. The only thing I see is that matching in the function head is 1.20x slower except with large maps (more than 32 keys).

inputs =
  defmodule Soda do
    @counts 1..10_000
    def inputs do
      %{
        "large" => Map.new(1..33, fn n -> {:"key_#{n}", 0} end),
        "small" => Map.new(1..32, fn n -> {:"key_#{n}", 0} end),
        "single" => %{key_15: 0}
      }
    end

    def body_match(map) do
      %{key_15: value} = map
      value
    end

    def case_expr(map) do
      case map do
        %{key_15: value} -> value
      end
    end

    def dot(map) do
      map.key_15
    end

    def head_match(%{key_15: value}) do
      value
    end

    def run do
      Benchee.run(
        %{
          "body_match" => fn map -> Enum.reduce(@counts, 0, fn _, _ -> body_match(map) end) end,
          "case_expr" => fn map -> Enum.reduce(@counts, 0, fn _, _ -> case_expr(map) end) end,
          "dot" => fn map -> Enum.reduce(@counts, 0, fn _, _ -> dot(map) end) end,
          "head_match" => fn map -> Enum.reduce(@counts, 0, fn _, _ -> head_match(map) end) end
        },
        inputs: inputs(),
        pre_check: true,
        time: 5
      )
    end
  end

Soda.run()

So, this was fun but the conclusion is that there is no difference. Sorry for derailing the topic!

1 Like

Bahaha, so it is! D’oh :man_facepalming:

Thanks so much for sharing that! I’m really bad about benchmarking and don’t know how to use Benchee very well, so this is useful.

This topic has been derailed a couple of different ways. I feel ok about it since some semi-interesting stuff came out of an often-asked question about destructing.

Btw, did I ever tell you about this one time at an old job… :upside_down_face:

2 Likes

This benchmark is incorrect. dot is the slowest way to access the data

I think it deserves another thread

1 Like

2 posts were merged into an existing topic: Benchmarking dot