Hacker News new | ask | show | jobs
by mcmatterson 2224 days ago
The use of prior matches in guards is super useful for parsing TLV style payloads where you first match on a size/length field and then match on the value itself as that number of octets. Does anyone have any insight into how / when similar support will make its way into Elixir?
1 comments

I think it's already there?

    Erlang/OTP 22
    Interactive Elixir (1.10.3)

    defmodule Test do
      def matchme(value) do
       x = :foo
       %{^x => bar} = value
       IO.puts(bar)
      end
    end

    > Test.matchme(%{foo: "bar"})
    bar
    :ok
My goodness, you're right (well, your example isn't, but the feature I'm talking about has been present since at least 1.8 (the earliest install I have handy):

  > <<len::8, val::binary-size(len)>> = <<3>> <> "abc"
    <<3, 97, 98, 99>> 
  > val
    "abc"
I don't know how I'd missed that.

(Your example demonstrates 'pinning' which has been a thing since always I think).

Your example is not what EEP-52 adds. The code that didn't used to compile before Erlang 23 is when the `size` attribute of a match-segment requires an expression (i.e. math) to be resolved.

One common use-case, is where a payload's prefix-length field encodes the number of bytes of payload minus one. You'll see this in many protocols as an optimization: if the payload is always at least one byte, then one byte of payload can be represented as a length of 0, allowing up to 256 bytes of payload rather than 255.

Here's an example of how the new syntax would be used for that, from the EEP-52 proposal (http://erlang.org/eeps/eep-0052.html):

    example1(<<Size:8, Payload:(Size+1)/binary, Rest/binary>>) ->
       {Payload,Rest}.
Before v23, you'd have to do this instead:

    example1(<<Size:8, Rest0:binary>>) ->
      PayloadSz = Size + 1,
      <<Payload:PayloadSz/binary, Rest1:binary>> = Rest0,
      {Payload, Rest1}.
Besides the reduction in code, I believe that the old code has an optimization fence (the math expression) that the new code doesn't—you get more optimized code out of the newer expression, because BEAM's runtime loader gets a bigger contiguous chunk of bitstring ops to specialize across.

Oh, and since the newer code is all in a head-clause, if it fails on matching Payload, it'll move on to attempting to match on the next head-clause, rather than generating a badmatch error. Just like if you produce an error in a head clause's guard clause expression.

-----

On a separate note, despite this example being simple, you can actually do arbitrarily-complex things:

    example2(<<HeaderSize:8, BodyChunkSize:8, BodyNumChunks:8,
               Payload:(HeaderSize + (BodyNumChunks * BodyChunkLen))/binary,Rest/binary>>) ->
       {Payload,Rest}.
oh prior as in lexically prior. The example in the erlang release I believe uses something that looks like what I wrote. I use the sort of matches you are talking about extensively in network packet size matching for a project I'm working on.