spencer.christensen

spencer.christensen

Why can't I use a variable to the left of `<>` in a pattern match? Compiler or Theory?

iex(26)> "hello " <> matched = "hello world"
"hello world"
iex(27)> matched
"world"
iex(28)> matched <> " world" = "hello world"
** (ArgumentError) the left argument of <> operator inside a match should always be a literal binary because its size can't be verified. Got: matched

I’m new to Elixir. I understand the basics of pattern matching. I am trying to understand conceptually why the above doesn’t work.

My understanding is that the evaluation of a pattern moves left → right. Therefore, when "hello " <> matched is evaluated the compiler is given a fixed starting place. Since it has a fixed location at the start, it can infer anything moving forward as it knows where it started.

The opposite, matched <> " world" = "hello world", fails. To my human brain this seems like a very simple match operation. However I understand that compilers and human brains have different skillsets and abilities.

I’m fine with this not working in Elixir. I don’t need it to. My main question is whether this a case where the compiler could theoretically be rewritten to have some sort of logic like “if pattern starts with variable, go right → left” or is there a deeper problem here where going “backwards” is a Hard Problem in computer science (CS)? I’m a self-taught dev so please forgive me is there is a rather obvious answer from a formal CS background.

Thanks! :pray:

Most Liked

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Hey @caslu I think @ken-kost’s answer is more accurate here. Matching isn’t a search of a binary rather it’s a “destructuring” of the binary where you’re pulling out a specific offset from the start into a value. This requires knowing what that offset is. Technically this does NOT need to be at compile time, you can do variable length offsets eg:

iex(2)> n=1; <<stuff::binary-size(n), rest :: binary>> = "hello world"; stuff
"h"
iex(3)> n=2; <<stuff::binary-size(n), rest :: binary>> = "hello world"; stuff
"he"
iex(4)> n=3; <<stuff::binary-size(n), rest :: binary>> = "hello world"; stuff
"hel"

However the point is that at the time the match executes the offset into the binary is known.

derek-zhou

derek-zhou

A smarter compiler might be able to do what you want; however, allowing variable at the beginning of a binary match will open a big can of worms. For example:

x <> "b" <> y = "abba"

Which one is correct?

  • x = "a", y = "ba"
  • x="ab", y="a"
sabiwara

sabiwara

Elixir Core Team

I don’t think it would be impossible per se, here is a proof of concept where you can achieve something similar:

message = "hello world"
suffix = "world"

case message do
  <<prefix::binary-size(byte_size(message) - byte_size(suffix)), ^suffix::binary>> -> prefix
end
# "hello "

(I’m cheating because I’m using the message variable from the parent scope, so this wouldn’t be viable as an implementation strategy).

The real problem I assume would be to be able to do it efficiently especially when dispatching over multiple clauses. Binary matching is very optimized in its current implementation, nimble_parsec is a great example leveraging it.
I found this paper when looking for resources explaining how it is being optimized, quite interesting.

spencer.christensen

spencer.christensen

@benwilson512

That use of dynamically sized binaries is super interesting! Your observation that matching isn’t searching but is destructuring is a very helpful one.

I did the next logical thing and broke it on purpose to see the error message with your use of binary-size:

iex(2)> n=3; <<stuff::binary-size(n), rest :: binary>> = "hello world"; stuff
"hel"
iex(3)> n=3; <<rest :: binary, stuff::binary-size(n)>> = "hello world"; stuff
error: a binary field without size is only allowed at the end of a binary pattern, at the right side of binary concatenation and never allowed in binary generators. The following examples are invalid:

    rest <> "foo"
    <<rest::binary, "foo">>

They are invalid because there is a bits/bitstring component not at the end. However, the "reverse" would work:

    "foo" <> rest
    <<"foo", rest::binary>>


└─ iex:3

** (CompileError) cannot compile code (errors have been logged)

What a lovingly crafted error message.

@derek-zhou

Ok, this is what I was getting at. I sensed there was some deeper “this is a Hard Problem :tm:” type of thing going on and you certainly highlighted an example.

x <> "b" <> y = "abba" is obviously impossible to destructure “correctly”.

But, I wonder, is x <> "ba" = "abba" impossible to destructure? Could something be programmed in the language itself to use negative offsets or is there some CS theory going on where backtracking is too potentially resource intensive etc.

derek-zhou

derek-zhou

No, but there is another can of worms that you may encounter. What if there is a multi-byte UTF-8 sequence end with the byte “b”? After matching you could have a x that is not a valid UTF-8 string.

For strings, you always want to match from left. You could do right side match with regular expression but that should be frown upon.

Where Next?

Popular in Questions Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
logicmason
Hi there, I'm working through my first release with elixir/phoenix. I've built a release with distillery and found that it crashes when I...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
wernerlaude
In AR this is so simple @articles = current_user.articles How to do in Ecto? def index(conn, _params) do current_user = conn.assig...
New

Other popular topics Top

lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New

We're in Beta

About us Mission Statement