What infix operators would you like to see in Elixir?

In Elixir, operators are also simply functions. A unary operator takes a single argument, a binary infix operator takes two.

It is therefore possible to define our own. However, the compiler determines which strings of characters are allowed as operators, so there is only a small set of possibilities.

Most of these are already defined in one way or another in Elixir’s standard library. In the end, the only operators that can be used without overriding an operator either in Kernel, Kernel.SpecialForms or Bitwise are:

\\ (which is also used to specify a ‘default’ argument in function clauses),
<- (which is also used inside for-comprehensions)
| (which is also used to construct/pattern match lists)
and
~>, <~, ~>>, <<~, <~>, <|>.

So: When you want to unequivocally define an operator in Elixir, you’re restricted to this set of squiggely arrows.

I personally think that this list is rather small. I totally agree that Haskell’s approach of letting you define any combination of non-alphanumeric symbols means that it is impossible for an outsider to comprehend the code, as operators are very implicit (you cannot find out what they do just by reading them).

On the other hand, operators ensure that code is very concise, and some types of operations are easier to read/write when written in an infix style, rather than a prefix one.

Elixir’s currently only allows a very small number of user-definable operators, I find this list to be rather short. Multiple times now, I’ve wanted to implement an operator for my library, and I was unable to find a good one.

On a related note, when two libraries both want to add an operator, and they end up using the same one, you cannot use those libraries in your module at the same time (unless you call one of the operators remotely).


So: What operators would you like to see being added (as free, user-definable operators) to Elixir?

2 Likes

Oh, before I forget. I am sure that other people had/have this same question:

why doesn’t Elixir define ** or ^?

The answer to this, is that these two operators are, in other languages, often used for a specific operation:

  • x ** y is the power operation, lifting x to the y-th power.
  • x ^ y is the boolean XOR-operation.

These operators could be implemented. However, it is (as of now) impossible to implement them in a way that would allow their usage in Guard expressions, because of some restrictions/missing features in Erlang.

As this is sure to confuse many people, the Elixir Core team decided to not use these operators. (in fact, ** was removed as a definable operator before v 1.0).

2 Likes

I personally think it is good to keep the global operator set highly restricted.

HOWEVER Within a macro context I wish you could define whatever you want. Being able to define, say, a PEG parser as:

  parser = PEG.gen_parser do
    char = ('a' - 'z') | ('A' - 'Z')
    chars = char *
    ws = ' ' | '\t' | '\n'
    sentence = (chars ws)*
  end

Would be amazing! Perhaps some kind of deftmacro (macro that receives tokens instead of an AST) or something…

EDIT: Really though, imagine this being a highly optimized and reduced computation (perhaps compiled and offloaded to a port or something to use OpenCL or so):

  MathEx.execute scalars: [offset: 0.5], input_var [k: get_big_array(), j: get_another_big_array()] do
    (-2.5 + 3.5*(k/3500.0) ^ (-1.25 + 2.5*((j+offset)/2500.0)))
  end
3 Likes

Why not just use APL? Sorry I believe something like this would very quickly result in potentially large amounts of unreadable code. Perhaps not for the writer but definitely for future maintainers. It would be the major issue with macros on steroids.

4 Likes

True, but you can still do that ‘now’ even:

defmacro something(stuff) do more_stuff end

something """
Let's make our
own DSEL in here
that will get compiled
to direct elixir code
"""

And yes, it could easily be horrible, but DSELs are amazingly useful in many cases.

1 Like

Takes judgment to know where to best use macros. Good judgment comes from experience and experience comes from bad judgment. The problem is that our bad judgment lives on forever in code others have to maintain.

5 Likes

Wouldn’t a macro solve the same problem? The trade off in reasoning (or lack of) about sparsely used macros would be the same as a custom operator. At the very least, named infix operators might not be a bad idea, but doing custom infix operators would hurt the approachability of the language I think.

4 Likes

As for macros and embedded domain specific languages: You can either take the approach using quote/unquote, which limits you to whatever is, in theory, valid Elixir syntax (regardless if functions/variables are actually defined), but which is probably faster as Elixir’s built-in shunting-yard algorithm is used. It also would make it easier to make things interoptable (cross-compile between Elixir and YourSpecialEDSL)

On the other hand, interpreting a string lets you write syntax in whatever way you want, but this does mean that you have to re-invent everything yourself, and that it is less compatible with Elixir’s built-in syntax.


But this is only tangentally related to infix operators.

I have built a Rational Numbers library, which in the end uses <|> to compose the numerator and denominator into an opaque Ratio struct. This works really nice, but <|> is not the most suitable operator for this (it is, however, better than one of the squiggely arrows which is all that is left). It still feels weird for me to use it, as <|> means ‘function application’ in some other languages like Haskell.

I’ve seen other libraries, such as Math, define <~> to mean ‘approximately equal to’ for floats.

I’ve never seen anyone define any of the other arrows. I’d love to hear how they are used in the wild. (GitHub is unfortunately only searchable with alphanumerics :sweat_smile:)

Here are the operators that I would like:

  • <%>, which I’d like to use in the Ratio library instead of <|>.
  • More variants with a character between two angle brackets, like <+>, <*>, </>, <&> or <?>. It seems to me that the syntax of ‘something between two angle brackets’ could be a clear indication that we’re using a ‘custom’ operator.
1 Like

Looking at this description I’m thinking factory function (in JavaScript terms); somehow using an infix operator wouldn’t have occurred to me. And ultimately actual division is implemented in terms of a function

iex> 5/2             
2.5
iex> Kernel./ 5, 2  
2.5
iex> Kernel.div 5, 2
2 

This discussion makes it quite clear that you feel that infix notation adds expressiveness. That’s fine but it also needs to be acknowledged that the support of infix operations comes at the cost of additional complexity - most notably precedence (management).

Easier doesn’t necessary mean better. I think we can agree that most of us find infix notation more familiar as a result of our math education in the past. My personal experience was that once I had fiddled with Clojure long enough to get past my parentheses-noia I realized that I didn’t miss infix notation, in fact the code seemed more consistent without it because the function/operator always appeared in the same place - the function position. pre-Clojure I couldn’t have imagined coding without infix notation, post-Clojure infix operators seem overrated, possibly a nice-to-have feature that needs to justify its complexity burden.

Given that most of us are familiar with some infix notation it seems a reasonable compromise for convenience sake to support a fixed set of common infix operators out of the box without going down the rabbit hole of supporting arbitrarily defined infix operators - especially in an environment where functions are the first class citizen.

1 Like

Let me restate, as easier is a very ambiguous term.

I would argue that some operations are a lot more readable when using infix notation over prefix notation. The most notable example in Elixir right now is the pipeline operator |>.


As for using a factory function over an operator for rational numbers: I decided on an operator as I was trying to mimic what Haskell does with rational numbers, and what Elixir already does with ranges, which is to implement them as a data structure, but have the inspect output be more concise. I like 2 <|> 5 over %Ratio{denominator: 2, numerator: 5}.

1 Like

Another vote here for favoring a readable code style. I have read a lot of Scala libraries that are incomprehensible, seemingly written to minimize the number of characters typed. I encourage a style of writing that optimizes comprehension. Unless there is a clear case to do so, I would encourage MyUtils.doThisSpecialThing over a custom operator.

3 Likes

Absolutely, hence why I personally think that the number of operators (infix or binary) should be limited outside of macro’s. But inside macro’s they should still be allowed as the purpose of many macro’s is to make a DSEL for specific purposes, and if that is, for example, to make a matrix-efficient DSEL, that is the difference between a few well-known mathematical operators compared to huge and unreadable in such a context function calls.

For something that is not specific to and well-known to a specific domain though, custom operators should absolutely be avoided.

1 Like

Don’t get me wrong ultimately you(r team) has to be happy with the choice you make and there rarely is a way to please everyone.

Even readability is context and situation sensitive. If somebody is reading Elixir code they should be comfortable with functions but an oddball operator (generally speaking) could throw them.
(And in reference to Haskell I regularly come across testimonials of how code from experienced Haskeller’s is typically inscrutable to beginners; How to make your Haskell code more readable to non-Haskell programmers).

Again - it depends on your background. Clojure’s thread-first and thread-last macros are used under similar circumstances, they often bewilder beginners but they are used everywhere - and they are even more terse than the Elixir pipeline operator because you don’t have to keep repeating ->> between expressions. In the end its “just syntax” - you accept it for what it is and move on.

Even personal taste changes with time. Coming from a C background semicolons seemed incredibly important to me for the longest time. These days having to type commas between parameters can seem excessive to me :grin:.

The operator is terse while creating a struct is verbose, though that could be hidden away in a function. One could ask whether 2 <|> 5 conveys the intent of creating a new structure (if in fact that is even an issue). Some people prefer functions over operators because they feel function names can more effectively convey intent. Then there is the issue of the implicit meaning of the characters involved through established previous usage and what effect that has - e.g. Vertical Bar usage - Computing. 2 <|> 5 may not immediately evoke the notion of a rational number but at least it should be fairly easy to remember for anyone already familiar with the module.

The actual main reason for picking 3 <|> 5 over %Ratio{denominator: 5, numerator: 3} is to hide the internal implementation. I do not want people to pattern match on what the numerator would be, because it doesn’t make sense to do so (outside of the internal functions of the library, which for instance make sure that a rational number is always in its simplest form).

I also do not want people to construct a %Ratio{} struct on their own, without going through the constructor function, because this constructor function is what makes sure that a rational number is at its simplest form, and that someone doesn’t try to create a number that with a denominator that is 0 (which would be, in fact, a division by zero).

I completely agree with you that there are many cases where it would be ill-advised to implement something as an operator. For exactly the same reason, Elixir only allows single-letter sigils. Neither Operators nor Sigils solve all problems. But both are important to solve a specific narrow set of problems in a graceful way.

I don’t believe that there are many people that disagree about Haskell being the other extreme; It has too many operators. In fact, any unicode symbol can be used as/in a valid operator nowadays

Elixir, however, is prided by its explicitness and the fact that documentation is a first-class citizen. When there is an operator I don’t know being used in a module, I know for sure that it is defined in a module that is being imported or used in the current one. I can fire up IEx to try out h ImportedModule.<|> and see exactly what is going on in great detail.


So, let me recap.

  • I am not of the opinion that we need more operators so we can solve all our problems by using operators.
  • Instead, I believe that when faced with the kinds of problems that operators are a graceful solution for, being able to choose from something that isn’t a squiggely arrow would make it easier to understand what’s going on, especially because many character(combination)s have an established meaning.

Arrows, for instance, have the established meaning of data flowing from the tail towards the point. Which makes them unsuitable for many operations that either compare the lhs with the rhs or combine them in some way.

1 Like

Yes, of course you can do that. It actually has the benefit that you separate your DSL from Elixir so it is much clearer what is what. Otherwise you are basically writing something which looks like Elixir but isn’t because of the operators.

And yes I will admit I am rather strict on that if you want a DSL then make a real one and don’t try sneak it in to the surrounding language, in this case Elixir.