rcm765

rcm765

Writing a keyword list guard?

I’d like a guard to determine if the value is a keyword list or not. I’ve tried the following macro:

defmacro is_keyword([{key, _value} | _tail]), do: quote do: is_atom(unquote(key))

but when I pass in a keyword list I get this error: ** (FunctionClauseError) no function clause matching in is_keyword/1

however if I remove the pattern matching in the function signature everything seems to work ok. So I’m lead to believe that pattern matching isn’t permitted in guards. If that’s the case how best can I write a single guard for keyword list detection? (yeah I know it should be a recursive function)

Most Liked

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

But in only works that way in guards for fixed compile time lists. It doesn’t work with runtime lists.

EDIT: Elaborating a bit now that I’m not on mobile.

You can do:

def foo(x) when x in [1,2,3] do

which will turn into

def foo(x) when x == 1 or x == 2 or x ==3 do

but what you can’t do is:

def foo(x, list) when x in list do
jhogberg

jhogberg

Erlang Core Team

No, the restriction is artificial. User-defined guards running arbitrary code would not be difficult to support from a technical point of view (we already do it, see prim_eval.S and its interaction with :erl_eval), it’s just very annoying to define what’s supposed to happen in corner cases like such a guard running a receive while in a receive, how exception tracing is supposed to work, and the likes.

So why don’t we support it?

Partially because it’s a bit of a footgun. If you end up throwing a user-defined guard into an infinite loop, which specific clause was it “called from?” If you accidentally make it side-effecting, which combination of the (say) three hidden invocations derailed things?

The main reason however is historical and there’s been no pressing need to change it. You can case over the result in the function body, and that’s been enough so far.

christhekeele

christhekeele

This is a non-starter (there is a reason the language doesn’t come with one).

TL;DR:
You cannot reliably fully traverse a list fast enough to be viable in guards.
As such, you cannot exhaustively ensure every item of a list is a Keyword pair in a guard.


Keyword lists are just lists (of atom/any two-tuples {atom, term}), which are singly-linked car/cons cells that require O(n) time to traverse.

Matches and guards are required to have near-constant-time access to data structures, since they are used in places in the VM with tight performance requirements (like selecting a function head to invoke from many given certain arguments).

This is why tuple and map indexing are allowed in guards (with integer-based elem(tuple, n) and key-based is_map_key(map, key) or :erlang.map_get(map, key)), their structures offer constant-time access to the data they compose. (Also why you can destructure on them in matches.)

I say “near” constant-time because you are also allowed to “peek” into the heads of O(n)-traversable data structures when such an operation is known to be fast and bounded (with hd in guards for lists, binary_part in guards for binaries, and destructuring in match patterns: [first | rest] for lists, "start" <> rest for strings, <<leading, rest::binary>> for binaries).

In practice this peeking is just enough to enable function clauses to recursively consume these O(n) datastructures piece-by-piece, using matching and guards to decide how to handle each chunk. Useful enough, we make concessions on the constant-time constraint.

However, fully accessing every item in a list is an unbounded operation, so, not allowed—even a theoretical “recursive” implementation (that guards are, by design, not expressive enough to support).

What your original macro (and what @Schultzer’s guard) attempts to do is just evaluate the first “peeked” term of a list for Keyword-iness, which could be useful in some contexts, but is generally insufficient to ensure that every item in a list is a keyword pair. And you can’t traverse every item in a list in guards, for reasons explained above.

Where Next?

Popular in Questions Top

tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
chokchit
** (DBConnection.ConnectionError) connection not available and request was dropped from queue after 2733ms. You can configure how long re...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
earth10
Hi, I’m just starting to build a side-project with Elixir and Phoenix and doing some basic test with Elixir alone. What strikes me is th...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

Other popular topics Top

marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29305 241
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
RisingFromAshes
I've read in another post that it may be possible with a router helper - but I couldn't find an appropriate one, and tbh, I'm still just ...
New
nobody
Hi! In PHP: $SERVER['SERVERADDR'] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
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

We're in Beta

About us Mission Statement