If you could change one thing in Elixir language, what you would change?

@josevalim for me, the simplest solution to make it less magical and more like “normal” macro is to wrap arguments in [] as this:

for1 [x <- list, y <- another_list, x < y] do
  IO,puts x
  IO.puts y
  x + y
end

Is legal syntax right now.

I think that what @OvermindDL1 meant is that this should be the only “magical” statement known by the compiler (aka Kernel.SpecialForms), not that case itself should become “more minimal”.

2 Likes

It’s good as it is, just using it as an example of an ‘already minimal construct’ that many other things could be built on it. :slight_smile:

Assuming a changed elixir language (or going back to 1.5 parsing days), I’d like a syntax like how my comp is, so for that example then something like:

for do
  x <- list
  y <- another_list
  true <- x < y
  IO.puts x
  IO.puts y
  x + y
end

Although I’d think indenting non <- operations a bit more might be more clear, played with the idea recently:

for do
  x <- list
  y <- another_list
  true <- x < y
    IO.puts x
    IO.puts y
    x + y
end

I.E. a formatter rule that if the top level expressions in a do block contain a <- then all <- should be at normal level and all others are indented one further, similar to how -> works. Another operator (an unary operator perhaps? Although I like the ability to let a filter be a full match expression) would probably be better for filtering though, although most of my filters are not boolean expressions but are rather full matcher ({:ok, blah} when blah>42 <- blah_thing()) but that could just become normal non-filter statements if non-list values are always wrapped or something (though in that case going to a full monadic-style do pipeline is even more powerful). Or just keep all indentation at the same level, whichever, I’m not sure which I like better yet but I like how the indentation shows the ‘pipeline’ of work in each loop.

There are soooo many possible styles it could go. I’m just really not a fan of having to put 18 commas’s in my for/with expressions. ^.^;

My comp does it even differently still, by you having to explicitly state what each <- is doing, like:

comp do
  x <- list list
  y <- list another_list
  true <- match x < y
  IO.puts x
  IO.puts y
  x + y
end

Which has the side effect that it can generate more efficient code (you can do even do something like {:ok, thing} <- MyModule.blah bloop to handle some custom type very efficiently without needing to go through Access calls or converting the custom type to a list or map or something). :slight_smile:

I could even be up for that as then it just becomes for/2 always (maybe even take options too!), although the fact it’s a list seems very confusing as to how the ‘scope’ of one element leaks into another element.

EDIT: The for final body doesn’t even need to be in the same body either, could do:

for do
  x <- list
  y <- another_list
  x < y
after
  IO.puts x
  IO.puts y
  x + y
end

(Or something else other than after, it’s just an example, maybe finally or so if that were added in a hypothetical Elixir syntax change.)

Yes exactly. :slight_smile:

1 Like

Isn’t it a bit weird that the first two are generators and therefore the right side are enumerables and the second is a filter but it uses precisely the same syntax? What if the filter expression returns a map? Do we use it as a filter (i.e. truthy value) or as a generator? I think we need two distinct syntaxes here.

Yep! Which is why I would really love a new unary operator for it, could just do something like filter x<y (which could work for matchers too like filter {:ok, blah} = something(x)) but the word is easy to ignore as ‘just another command’ where an operator would be explicit, could usurp @ perhaps but that already has a meaning so could get confused, etc…

Exactly, hence some new distinguishing operator or syntax. A binary operator could work as a filtering match expression but then a binary op could get ignored accidentally when reading the code unless it was sufficiently different from <-, but that is a possibility too. I rather like the idea of a prefix unary operator as then it could do simple not in [false, nil] test if a matcher via a noon-simple-binder = is not otherwise supplied.

Entirely agree! I couldn’t figure out something that I really liked in elixir’s current parser to use so that’s why my comp went with the explicit x <- match y style.

I would make parenthesis with functions a must, to remove the ambiguity and the warnings of the kind variable "something" does not exist and is being expanded to "something()".

10 Likes

Allow guards to use any function, not kernel module only

1 Like

Have you considered how ugly module and function definitions would look like then?

1 Like

Sadly not possible, as the BEAM doesn’t allow for this. This is mainly to allow reordering of match clauses in any match. And if guards were allowed to run arbitrary code, then they could have side effects and those would make reordering impossible. So the BEAM has decided on restricting guards to only a handful of functions that are known and guaranteed to be free of side effects.

4 Likes

Oh, I would use ASCIIDoc as a default documentation markup instead of Markdown. Former is much better suited for the job.

That’s a beam limitation, not an Elixir limitation. Elixir can actually use about anything you pass to it as a guard but the BEAM is what prevents its use.

I would make functions require parenthesis as well, although I’d make macro’s still have optional parenthesis, maybe something in its signature or name defines whether it should or should not have parenthesis enforced one way or the other.

Those are macro’s though, not functions. :slight_smile:

Yep, this right here!

ASCIIDoc is better than markdown true, but it’s still not a great documentation format. I’d rather Elixir used ReST (ReStructured Text). It’s already in heavy use for programming documentation, is significantly more extensible than ASCIIDoc and Markdown, and is still very simple to use (although the syntax does differ more than markdown/asciidoc).

2 Likes

Macros are functions.

Well technically a macro like:

defmodule Blah do
  defmacro bleep(), do: nil
end

Doesn’t exist as a function, rather it compiles to a :MACRO-bleep"/1 function and uses compile as some hoisted code, and if someone were to call ‘that’ function straight then sure, parenthesis should be required, but when going through the macro compile-time hoisting then parenthesis should not need to be as macro’s define the language. :slight_smile:

1 Like

But I might be using functions to build a DSL, just consider plug/2, it is a function and looks ugly with parens. It is a compile time construct without beeing a macro.

On the other hand side, pre defguard we wrote macros that shall be used like functions and definitively should have parens.

So I’m pretty against doing parens based on beeing a macro or not.

Instead a function should have metadata that tells the formatter if it is to use with or without parens. Similar to how a function in elisp can tell the editor how it shall be indented.

2 Likes

plug/1,2 is a macro, so that would still work. :slight_smile:

Yep, hence why I’d think macro’s should have them optionally, or it could even be forced one way or the other based on some attribute set on it or naming convention or so.

Rather being optionally on if a macro, just always parenthesis if a function. :slight_smile:

That would be attributes on a macro. I’m unsure when a function should ever not have parenthesis though, no case comes immediately to mind but it is possible…

EDIT: Although personally I’d like to see no parenthesis or commas for function args at all, only use them for disambiguation like in ML languages. ^.^

defmodule Blah do
  def add a b do
    IO.inspect a
    IO.inspect b
    IO.inspect (a + b)
  end
end
2 Likes

I think I get what’s your feeling, and deep inside my heart I feel pretty much the same. But the problem with this is that functions and macros are called pretty much the same way, so it would generate a lot of confusion if you could omit parenthesis from one and not the other. Take for example the macro IEx.break!/1,2 and the function IEx.break!/3,4. You would be able to omit from the first one, but not the second one, which is odd.

If any step is took in the direction of not allowing to omit parenthesis, my opinion is that it could be left as is only for a very specific case, like when you use the do ... end notation, since that’s the only case it looks REALLY ugly that comes to my mind. Maybe even make the parenthesis forbidden in those cases at all, this way these discussion could be totally mitigated, like most of them were when the formatter was out.

1 Like

That sounds like a code smell, they really should both be macro’s as they are acting more as a syntactical language extension than a function since they are operating on environmental state. Macro’s and function’s I don’t think should be called similarly, macro’s should only be used to extend the language in ways that a function cannot do.

Plus I’d still say that break! should require parenthesis regardless. ^.^;

I actually quite like them in those cases, like I can do (similar to code in my work project but not exactly):

Surface.link(ct_path(@conn, :index, @section.slug), do: @section.title)

Or I can do this:

Surface.link(ct_path(@conn, :check, @section.slug, @inv.id), style: :primary) do
  if @inv.checked do
    info = get_checked_state(@inv)
    "#{info.type}: #{info.summary}}"
  else
    "Not checked"
  end
end

And Surface.link is a function, not a macro, and I do indeed write it with parenthesis. As a comparison:

Surface.link ct_path(@conn, :check, @section.slug, @inv.id), style: :primary do
  if @inv.checked do
    info = get_checked_state(@inv)
    "#{info.type}: #{info.summary}}"
  else
    "Not checked"
  end
end

Which does compile and work fine, but it looks really weird to me, the whole style: :primary do section specifically.

As an aside, I still think the formatter is horribly broken on some constructs, like for/with/trailing-commas’s, etc… ^.^;
But then again it wouldn’t be an issue with for/with if they were more usual constructs too instead of magical multi-arity things. ^.^;

1 Like

True, your example proves I’m just wrong about the uglyness of parens and do ... end, so yeah, nevermind my last paragraph.

True, the formatter gets lost with this because a human would get even more lost most of the times.

So changing my opinion now: it should stay as it is right now, I don’t think it hurts so much and the formatter already puts parenthesis for function and macro, so that’s alright.

But this discussion just make me think about another thing: maybe a clear way to distinguish macros calls from functions calls would be good. IDK, what do you think?

I mean, I learned to like the . on anonymous functions because it makes it clear it’s anonymous, and not a defined function declared on the module or imported. Maybe clearing the macro/function ambiguity would even make compilation faster, wouldn’t it?

1 Like

I’ve thought of that as well.

Elixir currently follows the Lisp style, where there is no difference in how they are called.

Other languages like Rust use special markers that have to be used to call a macro, specifically a trailing ! on the call like println!("blah").

There are definitely merits to both styles. I think I prefer having some differentiation, but then again that makes it hard to convert a function to a macro later if the need arises… Not something I’ve decided on yet… ^.^;

Hmm, it theoretically could make it faster, but I’m unsure to what extent it would help since it’s only an extra check as it is at compile-time (this is why macro’s have to be required first, so its actually pretty cheap as it is).

2 Likes

Is there a reason why you’d want to know which namespace the function was originally defined in?

1 Like