I understand the reasoning for the distinction and its roots in Erlang, it's just not very elegant to work with.
If you want to check the type of a thing, you always want the matching `is_<type>`. It can be used anywhere in your code, including guards. There’s no guesswork involved here. That is the consistent rule.
When you see a function with a `?`, look at the typespec and the function name—give it the argument(s) it expects and it will answer the question on the tin with a boolean. Again, there’s no guesswork. These functions can be used anywhere except guards—that is the consistent rule for boolean functions.
> Is the thing I want to test written as a macro or not?
I have never had to ask myself this question, and I struggle to parse it. Is the “thing” you want to test referring to the value or to the test you wish to perform on that value? Since you’re asking about macros, I’m assuming you mean the guard test. For that, just learn what guards are available[0] (you can also write your own :D ).
Depends. In this case there are definite rule in place which you can learn. Overall, that's definitely language's fault. On other note, Elixir actually very good at consistent naming
- standard library is designed, not meshed up and grew layer by layer like js/php abomination (erlang one is not consistent and it leaks sometimes)
- Pipe operator by its mere presence enforces correct order of arguments, even in 3rd party code
It’d be immensely more helpful and productive if coming into a language meant one would spend the time to read, learn, and understand that language’s conventions and standard library—and read the source code for it! It pays serious and continuous dividends, and is the fastest way to gain an intuitive sense for the language.
What you say is not obvious is, to me, a result of not reading the docs, guides, and source code of the language and its standard library. I have seen this pattern repeatedly—those engineers who have learned the language find these things obvious. It’s not an insult, but a push to fill in those gaps. Elixir and Phoenix both have some of the best documentation you will find.
There's nothing intuitive about memorizing dozens of built-in functions that are (for opaque reasons) different from all the other built-in functions.
Because it is not meant to be a guard—it cannot further empower a function head and pattern match and be optimized and/or inlined by the compiler. Simple as that.
Not all expressions are allowed in guard clauses, but only a handful of them. This is a deliberate choice. This way, Elixir (and Erlang) can make sure that nothing bad happens while executing guards and no mutations happen anywhere. It also allows the compiler to optimize the code related to guards efficiently.[0]
> There's nothing intuitive about memorizing dozens of built-in functions that are (for opaque reasons) different from all the other built-in functions.I’m sorry, but I’m now struggling to believe you’re discussing this in good faith. Opaque reasons? I see none. It sounds like you either do not like or understand the reasons. It also sounds like you have an expectation that the existence of a boolean function means you can use it in a guard. But that’s your expectations not matching the language’s features and reasoning, neither of which are opaque. There’s nothing intuitive about memorizing dozens of functions? Assuming you’re a software engineer, that is your job. I probably have hundreds of functions memorized, across dozens of standard library modules, in multiple languages. Even if I don’t recall what specific options or arity a function has, I still have enough of it learned because that is my job. It’s what we get paid well to do.
[0]: https://hexdocs.pm/elixir/main/patterns-and-guards.html#guar...
First of all, that's not an explanation, that is handwaving. The real explanation is that URI.char_reserved? could be rewritten using defguard, because it only uses `in`[0]. An arbitrary choice (whether conscious or unconscious) was taken, that this particular standard library function is not allowed to be used in guards. But there is no good reason for it.
Secondly, are you claiming that it is not useful to be able to use URI.char_reserved?/1 in a guard? That's obviously bullshit.
Finally: The real reason why some things can be used in guards and other can't, is that Elixir must be able to guarantee that no side-effects happen while evaluating a guard[1]. This is a good reason (a good follow-up question is, "why must guards be side-effect free?") and something you can use to form a mental model of which functions you can use in guards and which you cannot, but it is not described anywhere in the Elixir documentation. It's not an easy thing to form a mental model around, but it can be done.
To be fair, I think this is a shortcoming in Erlang as well. It would be better to be able to look at the type of a function and be able to tell whether I can use it in a guard or not. Or just allow all functions to be used in guards, such as in Haskell.
0: https://gist.github.com/Munksgaard/ccac61310651d3402571506e4...
1: https://www.erlang.org/docs/22/reference_manual/expressions#...
I wasn’t hand waving, I was simply summarizing guards in general. We’ve both provided the same explanation regarding why some things can or cannot be guards —you’ve linked to Erlang’s discussion of guards; I’ve linked to Elixir’s. So we both understand guards and their limitations.
I’m not terribly interested in arguing about what should or shouldn’t be “guard-worthy”. The built-in guards don’t strike me as arbitrary choices, but as intentional choices as part of designing core language features—that’s all in Kernel, on which everything else depends.
The URI module, while a great part of the stdlib, is not core language guard material. I can say that in 7 years, I’ve never needed or wanted a built-in guard to check if a character was a reserved URI char—but I can believe some problem spaces might have use for enhanced guards. Why, I’ve written my own for having more readable and shorter guards for things like empty lists, maps, and more. The ability to compose those from core language guards and features is one of the many things that makes Elixir a great language for my use cases.
I agree with you that you have to build a mental model and you have to memorize (or consult) which guards are available, but arguably the `is_foo` vs `foo?` distinction here is a positive one, because it only takes a glance to know if it is a guard or not (and the majority of guards have the `is_*` prefix so you have to memorize fewer).
You are correct URI.char_reserved? could have been a guard - but that should not be a confusion point for a user of the library - it is clear it is not a guard function. The confusion only arises as a contributor once you read the source as to why a certain choice was made (in this particular case, char_reserved? was written before defguard existed, but even if I wrote it today I am not sure I would consider the possibility users would invoke it in guards).
Btw, I know you know this. I just hope this clarifies the whole discussion a bit for others!
is_integer is a guard (in pseudocode: cell.type == TYPE_INTEGER).
URI.char_reserved? is too high level to be builtin the VM.
Same for the hypothetical is_keyword_list mentioned above. How would you know if something is a keyword list?
1. It is a list
2. The first element is a tuple of 2 elements
3. The first element of the first tuple is a keyword
4. Every other element in the list obeys #2 and #3
What if you pass a list with a million elements to a guard like that? The VM would grind to a halt traversing every single element to make sure the whole thing is a Keyword. This is why there is no such guard.
So sure, you might have to consult the docs for what's a guard and what is not, but can also be understood intuitively.
> I was not being rude.
Translation: the person asking needs to RTFM because they have a problem with something that is obvious or I don't understand and it doesn't matter which. This isn't being rude.
> It’d be immensely more helpful and productive if coming into a language meant one would spend the time to read, learn, and understand that language’s conventions and standard library and read the source code for it!
Translation: the person asking is wasting time, being unhelpful and unproductive asking questions
> I still have enough of it learned because that is my job. It’s what we get paid well to do.
Translation: the person asking must not be doing their job because they don't understand things the way I do (I'm a software perfectionist, ofc)
I believe The Erlang (and through extension, Elixir) community seems to engender and defend this kind of back-handed approach to "helping". It doesn't just come off as elitist, it is often little more than taunting. Suggesting that someone pours over documentation (and laughably source code) to explain patterns (or a lack thereof) in a highly abstracted language is counter-productive.
For the record, I'll just hide your posts from now on as you cannot seem to help yourself bob.
However, I'm a bit curious about when you say
> Suggesting that someone pours over documentation (and laughably source code) to explain patterns (or a lack thereof) in a highly abstracted language is counter-productive.
Isn't that what documentation is for? To learn about whatever's being documented. What would you suggest would be the better way to convey this information?
For source code, I can see your point. Although in my experience source code has the benefit that you can be certain that it doesn't lie. It does exactly what it says. Sometimes this can be a really nice benefit when trying to figure out what's going on. Regardless of the language or environment you are in.
I would agree, if everything was documented. The issue at-hand is what is not explicitly documented. What constitutes a guard-worthy function? ie A pure javadoc without any notation about what the API does, is not sufficient.
https://hexdocs.pm/elixir/1.15/patterns-and-guards.html#guar...
Plus the docs lists on the sidebar which custom functions are guards. Examples: https://hexdocs.pm/elixir/1.15/Integer.html#guards
> Translation: the person asking needs to RTFM because they have a problem with something that is obvious or I don't understand and it doesn't matter which. This isn't being rude.
That’s an incredibly uncharitable translation and doesn’t accurately match my meaning or intent. At each point in this thread, other commenters and I have provided explanations and linked documentation. Each response has been of the form “sure thing / that’s true, but it’s not obvious to me”. Then a repetition of the core complaint. This has led to more attempts at explanation and linking documentation, just to get the same response. Saying RTFM is one thing, but it’s simply not rude to suggest reading docs would bring clarity (and suggesting reading docs would help is not the same as answering “RTFM”).
> Translation: the person asking is wasting time, being unhelpful and unproductive asking questions
Not at all. I haven’t once felt like the person asking was wasting time, being unhelpful, or being unproductive asking questions. My comment was simply saying that it is immensely more helpful and productive [to a person entering Elixir for the first time] to read Elixir docs and guides and, where accessible, the language/stdlib source (it’s all Elixir), because it pays serious and continuous dividends on the journey of mastering the language—compared to jumping in deep before you’ve built an intuitive understanding of how it works. It wasn’t a slight or an insult.
It was the same advice I received early in my career in a completely different language that has proven itself true with every language I’ve learned since.
> Translation: the person asking must not be doing their job because they don't understand things the way I do (I'm a software perfectionist, ofc)
Amusing weaponization of my HN profile, but your translation is still unfair. Complaining about memorizing a handful of guard functions felt to me like complaining about doing the job. I don’t expect anyone to understand things the way I do. Nor was I trying to backhandedly insult or taunt.
I spend nearly half my working hours each week reviewing thousands of lines of Elixir code from hundreds of engineers, pairing with 10s of them, helping people learn, explaining how things work, and learning new things myself alongside them every day. Every time anyone doesn’t understand something, it’s an opportunity for us to pair up, dig in, review docs, come up with new explanations, show off code to explain and cement concepts, etc. That’s not the same thing as complaining and labeling something as a language problem, have multiple people trying to help explain it, but keep saying “but it’s not obvious and doesn’t match my expectations, so that’s Language X’s fault.”
It's pretty rude to assume/insinuate that I haven't read the documentation just because you misunderstood my initial concern.
I think you did a fine job, and I also commend you for having thicker skin and not responding to the personal attacks on you in-kind.
It also stands in sharp contrast to how others in this thread have responded, which I think could easily be used as an example for the 'welcoming' element. Now, it is possible that you are for some reason personally irritated at 'newbies asking stupid questions' but consider that once upon a time you too were a newbie and that there are many fields where you are a newbie today and where talking to people and asking questions will serve as an accelerator to gaining knowledge because interaction is a much faster path to clearing things up than staring at the documentation.
Whether Elixir and Phoenix have 'some of the best documentation you will find' is besides the point, that only shifts the line at which interaction with others becomes the main driver of further advancement. And that good documentation wasn't written to serve as a hook for a putdown for new learners of the language.