What threw me off with Elixir was multiple inconsistencies, some of which documented in this article (btw, Elixir didn't fix the issue Joe mentioned about FORMS ≠ EXPRESSIONS).
Another one is this: When you first learn Elixir, you start to like the idea of calling functions from their types, like Tuple.product({2, 4}). Now if you want to get the size of a tuple, what do you do? Probably Tuple.size(...), right?
Wrong. You should use tuple_size(...)!
Turns out sometimes you gotta use these "orphan" functions even though they logically belong to a certain type (in this case, why isn't tuple_size called like Tuple.size or something?)
> Turns out sometimes you gotta use these "orphan" functions even though they logically belong to a certain type (in this case, why isn't tuple_size called like Tuple.size or something?)
tuple_size/1 is a guard, and guards are built-in. The compiler itself uses them. Unlike regular functions, you are allowed to use guards in a function head, like:
def foo(my_tuple) when tuple_size(my_tuple) == 3 do ...
Ahh, that's it. I said in a sibling comment that I had no idea why it was like that. But reading this made me realize that at one point I did know why! Completely forgot this bit of knowledge in the last year of not using the language.
Oh yeah, FWIW a "built-in" function or macro just means it's a part of the Kernel module, which is automatically required+imported in Elixir modules. So it's not orphaned, it's just in the one special module that gets brought in by default.
I guess it has something to do with those functions frequently used in guards.
_Notice tuple_size/1, map_size/1, byte_size/1 and some other functions/macros are defined in Kernel and documented in the section Guards[0]_
We can't invoke remote functions (which Tuple.size/1 would have been) in guards.
Though, there are some other functions that I can't think of a reason why they are "orphaned" such as put_elem/3 to put element into a tuple, while, for example, for maps there is Map.put/3
I didn't look it up for this one specifically but usually those are when elixir is just directly applying a function from the core erlang module (ie erlang global fns).
As for why they do that, rather than wrap them into the appropriate module, I have no idea. There may be a valid reason: for a language that is otherwise so careful about naming, organization, and consistency it would be a bizarre unforced error.
Some of them have nonstandard (for elixir) names and parameter order too so they are definitely one of the few unpleasant quirks of the language.
Another one is this: When you first learn Elixir, you start to like the idea of calling functions from their types, like Tuple.product({2, 4}). Now if you want to get the size of a tuple, what do you do? Probably Tuple.size(...), right?
Wrong. You should use tuple_size(...)!
Turns out sometimes you gotta use these "orphan" functions even though they logically belong to a certain type (in this case, why isn't tuple_size called like Tuple.size or something?)