Whitespace support for `with` clause

104 views
Skip to first unread message

Gilbert

unread,
Aug 24, 2016, 6:03:42 PM8/24/16
to elixir-lang-core
Hi, I am in the process of learning Elixir, and let me first say I am astounded at how polished the language and build tools are. A grand applause is well deserved to everyone who has helped Elixir get this far!

I have only a minor suggestion. It seems to be easy to do, so here it goes.

The `with` clause is excellent. This works, for example:

    msg =
      with {:ok, data}    <- read_line(socket),
           {:ok, command} <- KVServer.Command.parse(data)
      do
        KVServer.Command.run(command)
      end

However, I'd like to write it in this style, in effort to reduce line width (and, IMO, increase readability):

    msg =
      with \
        {:ok, data}    <- read_line(socket),
        {:ok, command} <- KVServer.Command.parse(data)
      do
        KVServer.Command.run(command)
      end

But, as you can see, I need to add a backslash after the `with` keyword for it to compile.

Is there a technical reason for the backslash requirement? Or could perhaps Elixir be updated to support this style of formatting, without the backslash? :)

I am using Elixir 1.3.2

Thanks for reading,
Gilbert

José Valim

unread,
Aug 24, 2016, 7:45:59 PM8/24/16
to elixir-l...@googlegroups.com
with is a construct like any other in the language, such as my_fun, your_fun and so on. It is bound by the same grammar rules and we don't plan to add exceptions. You should use the backslash or explicitly use parens.



José Valim
Skype: jv.ptec
Founder and Director of R&D

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/1d816043-d473-4ced-8194-6c1227232e99%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

OvermindDL1

unread,
Aug 25, 2016, 10:03:46 AM8/25/16
to elixir-lang-core
There is also the [Happy](https://hex.pm/packages/happy) library, which is like `with` but can do what you want (and more, while using `=` instead of `<-` while having the same features, and more).  I use it extensively

José Valim

unread,
Aug 25, 2016, 11:13:00 AM8/25/16
to elixir-l...@googlegroups.com


(while using `=` instead of `<-` while having the same features, and more)

Coincidentally that's the one thing I dislike about happy: the overriding of the operator =. <- was made to be used exactly in situations such like this and that's why it has no semantics outside of existing constructs. :)

Drew Olson

unread,
Aug 25, 2016, 11:22:38 AM8/25/16
to elixir-l...@googlegroups.com
Has there been consideration of adding a "happy-like" macro version of with (that has the same syntax as happy, but with <-)? I've also implemented something like this locally for my projects, I find it reads more pleasantly.

Obviously, this works fine for me but if many folks are interested in this syntactic form, perhaps including the alternative in elixir itself makes sense.

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscribe@googlegroups.com.

Gilbert

unread,
Aug 25, 2016, 11:27:02 AM8/25/16
to elixir-lang-core, jose....@plataformatec.com.br
Ah, I thought perhaps it was a keyword with a special grammar. This makes sense, thanks for responding :)

Gilbert
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.

José Valim

unread,
Aug 25, 2016, 11:40:49 AM8/25/16
to elixir-l...@googlegroups.com
No plans. If you prefer happy's approach, then use happy. :)



José Valim
Skype: jv.ptec
Founder and Director of R&D

OvermindDL1

unread,
Aug 25, 2016, 12:00:45 PM8/25/16
to elixir-lang-core, dr...@drewolson.org
On Thursday, August 25, 2016 at 9:13:00 AM UTC-6, José Valim wrote:
(while using `=` instead of `<-` while having the same features, and more)

Coincidentally that's the one thing I dislike about happy: the overriding of the operator =. <- was made to be used exactly in situations such like this and that's why it has no semantics outside of existing constructs. :)

Heh, that is one of the things that I really do like about happy, it unifies the 'matching' to be fully powerful in all cases.  For example instead of just doing:
```elixir
{:ok, str} = get_a_string_or_whatever()  # Does not make sure `str` is_binary

# -- or --
case get_a_string_or_whatever() do
  {:ok, str} when is_binary(str) -> :blah # This makes sure `str` is_binary, but wow is it wordy
end

# -- or happy's way --
{:ok, str} when is_binary(str) = get_a_string_or_whatever() # Enforce entire matching semantics, inline!
```

I do like `<-` and `->` but when I see them I think of list stuff (I'm an old erlang programmer) and certainly not inline matching enforcement.  Like using `<-` in for's makes sense to me, you are creating a new looping 'scope', where `=`'s do not.  I think the same thing in with/happy, the <- is creating a new looping scope, except that it isn't, there is no loop, no enumeration work, instead it just doing doing a match/when, which should just be done and supported by `=` everywhere as `=` is a matching construct.

Personally, I'd change Elixir so that `=` supports `when`, just naturally and everywhere, and this is in fact my main usage of `happy_path`, to enforce a specific match or fail out.  I would also make `<-` work only on comprehension-style things, where something will be looped (I have a few ideas for this too), and `->` only for case-style blocks, although personally I'd probably make case something like:
```elixir
case something() do
  {:ok, str} when is_binary(str) do
    something_in_a_body()
  end
  {:ok, _}, do: {:error, :invalid_type}
  {:err, reason} = e, do: e
end
```
Although personally not with 'do', but that is an easy to match construct in elixir, but the thing is that it makes defined blocks with a do/end instead of arbitrary `->` that just happens to end when another `->` is encountered.  It seems magical in how those rules work (even though I know how the parsing works internally).


Err, too much of a tangent, in essence:  `with` using `<-` makes me think comprehension-loops, not matching, and it bugs the ever-living-tar out of me especially since `=` is already the matching operator (just needs to be buffed to support `when`).  ^.^



On Thursday, August 25, 2016 at 9:22:38 AM UTC-6, Drew Olson wrote:
Has there been consideration of adding a "happy-like" macro version of with (that has the same syntax as happy, but with <-)? I've also implemented something like this locally for my projects, I find it reads more pleasantly.

Obviously, this works fine for me but if many folks are interested in this syntactic form, perhaps including the alternative in elixir itself makes sense.

Not done yet, but the author is *very* responsive and if asked I'm sure he would add such a variant (maybe call it `happy_with`?).  I really really *really* hate using `<-` as a matching construct, that is `=`'s job.  ^.^ 

OvermindDL1

unread,
Aug 25, 2016, 12:05:40 PM8/25/16
to elixir-lang-core, dr...@drewolson.org
Huh, I just realized why `with` bugs me so much, it looks like `for` but does not act like it, has nothing to do with enumeration, but the `<-` all over the place make it feel like it should.  The `<-` operator is not being used consistently across the language.  o.O

José Valim

unread,
Aug 25, 2016, 12:22:08 PM8/25/16
to elixir-l...@googlegroups.com, dr...@drewolson.org
# -- or happy's way --
{:ok, str} when is_binary(str) = get_a_string_or_whatever() # Enforce entire matching semantics, inline!

Unfortunately this requires mangling of the AST because "=" has higher precedence than "when". I wonder if happy path behaves correctly in cases such as:

IO.inspect({:ok, str} when is_binary(str) = get_a_string_or_whatever())

Or in cases such as:

{:ok, str} when is_binary(str) = tuple when is_tuple(tuple) = get_a_string_or_whatever()

And we cannot change the precedence as it would break cases such as:

{:ok, str} = tuple when is_binary(str) ->

I do like `<-` and `->` but when I see them I think of list stuff (I'm an old erlang programmer) and certainly not inline matching enforcement.

None of them are related to looping. -> is only used inside do blocks to specify clauses, such as case and receive (exactly the same as in Erlang). <- would be more correctly described as a "soft-matching" operator. In both for and with, they specify that the construct should not continue if there is not a match. The looping is not a property of <-, but a property of the enclosing construct (i.e. for/with). This is also inline with the usage of <- in monads (which could describe both for and with).
 
Personally, I'd change Elixir so that `=` supports `when`, just naturally and everywhere

I would love this too, it is one the things I attempted early on but the operator precedence would never work out. Which is what makes me wary of overloading =, specially if it requires mangling the AST.

OvermindDL1

unread,
Aug 25, 2016, 12:55:09 PM8/25/16
to elixir-lang-core, dr...@drewolson.org, jose....@plataformatec.com.br
Ooo, fantastic questions, I am curious, let's test.  :-)


On Thursday, August 25, 2016 at 10:22:08 AM UTC-6, José Valim wrote:
 
# -- or happy's way --
{:ok, str} when is_binary(str) = get_a_string_or_whatever() # Enforce entire matching semantics, inline!

Unfortunately this requires mangling of the AST because "=" has higher precedence than "when". I wonder if happy path behaves correctly in cases such as:

IO.inspect({:ok, str} when is_binary(str) = get_a_string_or_whatever())

Testing, indeed it is not valid there.  :-)

I wonder if it would be worth supporting such a case, I could report it in any case, done.  :-)


On Thursday, August 25, 2016 at 10:22:08 AM UTC-6, José Valim wrote: 
Or in cases such as:

{:ok, str} when is_binary(str) = tuple when is_tuple(tuple) = get_a_string_or_whatever()

Testing, and cool an unhandled case, reported that too.  :-)


On Thursday, August 25, 2016 at 10:22:08 AM UTC-6, José Valim wrote: 
And we cannot change the precedence as it would break cases such as:

{:ok, str} = tuple when is_binary(str) ->

Not unless `->` was removed from such a section and it became a call set like my case example, then the precedence could be fixed.  :-)


On Thursday, August 25, 2016 at 10:22:08 AM UTC-6, José Valim wrote:
I do like `<-` and `->` but when I see them I think of list stuff (I'm an old erlang programmer) and certainly not inline matching enforcement.

None of them are related to looping. -> is only used inside do blocks to specify clauses, such as case and receive (exactly the same as in Erlang). <- would be more correctly described as a "soft-matching" operator. In both for and with, they specify that the construct should not continue if there is not a match. The looping is not a property of <-, but a property of the enclosing construct (i.e. for/with). This is also inline with the usage of <- in monads (which could describe both for and with).

Hmm, soft-matching operator, makes sense.  Any chance on updating the docs at http://elixir-lang.org/docs/stable/elixir/Kernel.SpecialForms.html#for/1 where it states "Enumerable generators are defined using <-:" to something of that form, maybe like "`<-` matches on each given element and skips non-matches", although that still does not match how `with` does it.  Hmm, it seems that `for` and `with` are using an identical operator in incompatible ways.  Unsure how to resolve that without changing the form of one or the other...  (/me really dislikes operators that do different things in different areas)


On Thursday, August 25, 2016 at 10:22:08 AM UTC-6, José Valim wrote:
Personally, I'd change Elixir so that `=` supports `when`, just naturally and everywhere

I would love this too, it is one the things I attempted early on but the operator precedence would never work out. Which is what makes me wary of overloading =, specially if it requires mangling the AST.

Hmm, having `when` be directly adjacent to `=` in precedence, what side effects would that have?  I do not think that `=` is allowed adjacent to a `when` that I can think of in function heads (which are the only other case where `when` is used that comes to immediate mind) so no issue there.


This all gets me back to thinking maybe there is a way to pre-set forced matches.  Just playing with ideas, this is not a proposed syntax or anything:
```elixir
{:ok, str} = get_a_string_or_something() when is_binary(str)  # Hmm, nope, the `when` is too far away from the thing it is testing...

when is_binary(str) in {:ok, str} = get_a_string_or_something() # Hmm, requires an extra thing here designated by 'in' although I doubt you could use the actual 'in' here as it has uses in matching, but what word would be appropriate...

when is_binary(str) {:ok, str} = get_a_string_or_something() # Maybe a way to do it without the inner part, it is a single expression after all, but becomes noisy...

when is_binary(str)
{:ok, str} = get_a_string_or_something() # A more functionally way, put the 'spec' on the prior line, hmm...

@spec {:ok, String.t}
{:ok, str} = get_a_string_or_something() # I would so *love* if @spec/@type/etc... could be made first-class citizens of Elixir, 
# where the compiler could enforce certain things, like this thing would fail at compile-time unless get_a_string_or_something()
# will always return {:ok, String.t} or more refined in its function spec (where that spec is also verified via the call-path in the
# function, thus function-scoped type checking, fairly easy to do unless more detailed ones)
# However this method does not resolve the prior issue where those fail at run-time, not compile-time.

@spec {:ok, String.t} | MatchFailure.t
{:ok, str} = get_a_string_or_something() # Maybe this could allow both compile-time checks and runtime failure (via MatchFailure.t)
# if it fails to match, maybe the `|` could be `else` or something, could be as detailed or specific as wanting to allow, more specific
# like the `MatchFailure.t` would only allow a MatchFailure.t through, so if the `get_a_string_or_something()` function spec possibly
# had more then it would fail at compile-time unless handled via more or less specific matching.
```

I think what I really want is a(n even optionally) typed elixir.  I'd love to put `@spec`s on my variables, functions, everything, and have the compiler fail to even compile if they do not match.  That would catch the number one source of bugs that I experience by a significant margin.

José Valim

unread,
Aug 25, 2016, 1:22:15 PM8/25/16
to OvermindDL1, elixir-lang-core, dr...@drewolson.org

Testing, indeed it is not valid there.  :-)

I wonder if it would be worth supporting such a case, I could report it in any case, done.  :-)

My point is that changing the AST is a slippery slope. I will likely always find an example that fails because of precedence and mangling.

Hmm, soft-matching operator, makes sense.  Any chance on updating the docs at http://elixir-lang.org/docs/stable/elixir/Kernel.SpecialForms.html#for/1 where it states "Enumerable generators are defined using <-:" to something of that form, maybe like "`<-` matches on each given element and skips non-matches", although that still does not match how `with` does it.  Hmm, it seems that `for` and `with` are using an identical operator in incompatible ways.

Improving the docs is a great idea. And again, the skipping or not is defined by the enclosing with or for. All <- does is to check if something matches. All you need to know is that the left side is a pattern.
 
Hmm, having `when` be directly adjacent to `=` in precedence, what side effects would that have?  I do not think that `=` is allowed adjacent to a `when` that I can think of in function heads (which are the only other case where `when` is used that comes to immediate mind) so no issue there.

As mentioned in the previous email, it is related to the precedence of = and when on the left side of ->. Play with the grammar and let us know if you can make both "x when y = z" and "x = y when z -> w" work. Maybe if both operators have the same precedence and are left associative?



--

Ben Wilson

unread,
Aug 25, 2016, 8:34:39 PM8/25/16
to elixir-lang-core, overm...@gmail.com, dr...@drewolson.org, jose....@plataformatec.com.br
"/me really dislikes operators that do different things in different areas"

But that's exactly what happy makes the `=` operator do, and of all the operators in use `=` is by far the most used! `<-` effectively means "this is going to be bound according to the logic of the monad we're in". It isn't in common use outside of `for`, and `with` merely generalizes on that.

OvermindDL1

unread,
Aug 26, 2016, 10:04:40 AM8/26/16
to elixir-lang-core, overm...@gmail.com, dr...@drewolson.org, jose....@plataformatec.com.br
I have notes about `=` too, I just focus on `<-` more because it seems a bigger difference in usage to me.  ^.^

Wiebe-Marten Wijnja

unread,
Aug 29, 2016, 1:15:51 PM8/29/16
to elixir-lang-core
Put simply, `<-` and `->` both semantically mean 'bind at a later time'. 

In the case of `->` during the creation of an anonymous function,   it means:
 'replace (i.e. bind) all the matches at the pointy-side by the things on the shaft-side inside the function body on the pointy-side with the actual data that will be passed in when the function is called.'
In the case of 'for', it means:
 'replace (i.e. bind) all the matches at the pointy-side by the things on the shaft-side inside the comprehension body on the pointy-side with the actual data that will be passed in during each iteration (that matched and passed the filters).' 
In the case of 'with', it means:
 'replace (i.e. bind) all the matches at the pointy-side by the things on the shaft-side inside the succeeding with-clauses when it will be executed.'  (don't mind the pun... ;D) 

See a pattern here?

The reason there both is a `->` and a `<-`, is that it is more natural for certain kind of bindings to be written right-to-left, and others left-to-right.

The only reason that the match operator `=` works 'differently' inside function heads than it works inside function bodies, is that inside a function head, there is no notion of order-of-execution, while in function bodies there is (left-to-right, top-to-bottom). To have this clear order-of-execution inside a function body is a design choice (that both Erlang and Elixir follow) that makes the language a whole lot easier to understand for newcomers. Languages that are closer to the 'bare' lambda calculus like Haskell do not have an order of execution and consequently do not have this added restriction on `=` inside function bodies. But programming without an order-of-execution is something that takes some getting used to for people that come from the imperative realm. It also makes some things very hard to express.


So: `<-` and `->` are in essence the same thing, and `<-` does basically the same thing, regardless of using it inside `for` and `with`.
Also, `=` does not so much behave 'differently' outside of matches, as that it is more restricted. This restriction is because of Elixir's order-of-execution. This restriction also is a good thing, because it makes it sure that what you write is unambiguous.

Just my two cents ;-)

~Wiebe-Marten/Qqwy
Reply all
Reply to author
Forward
0 new messages