Capture operator and lack of &N

288 views
Skip to first unread message

Alex Rønne Petersen

unread,
Aug 4, 2013, 1:05:45 AM8/4/13
to elixir-l...@googlegroups.com
So I tried doing something like:

foo(&bar(42))

But got:

invalid args for &, expected an expression in the format of
&Mod.fun/arity, &local/arity or a capture containing at least one
argument as &1, got: bar(42)

It makes sense; the compiler can't possibly know what arity the
generated function should be. I wanted it to have one argument, but I
didn't want to use it. So here's an idea:

foo(&1/bar(42))
# Equivalent to:
foo(fn(_) -> bar(42) end)

This explicitly tells the compiler the function arity without having
to use any arguments. I don't so much care about the particular syntax
as long as it's terse, so better ideas are certainly welcome.

Thoughts?

Regards,
Alex

Yurii Rashkovskii

unread,
Aug 4, 2013, 1:16:06 AM8/4/13
to elixir-l...@googlegroups.com

I suspect creating even more terse syntax around this feature will only make the code less and less readable.

--
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-co...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


Alex Rønne Petersen

unread,
Aug 4, 2013, 3:23:57 AM8/4/13
to elixir-l...@googlegroups.com
I dunno, I think I have a reasonable use case.

I'm making a dependency-based build tool, sort of like Make. You can
define a .PHONY-like rule like this:

@phony [name: "clean",
sources: [],
recipe: fn(_) -> shell("%{rm} *.o") end]

The sources list is passed to the recipe function. However, in this
case the list is empty, so we don't care about it. It could have been
written much nicer like this:

@phony [name: "clean",
sources: [],
recipe: &1/shell("%{rm} *.o")]

This would also fit much better with how 'regular' Make-style rules
are written with this tool:

@rule [targets: "foo.o",
sources: "foo.c",
recipe: &shell("%{cc} -c #{&1} -o #{Path.rootname(&1) <> ".o"}")]

Alexei Sholik

unread,
Aug 4, 2013, 3:49:02 AM8/4/13
to elixir-lang-core
Support for &1/shell("%{rm} *.o") will allow one to generate an arbitrary function of N arguments that does nothing with them. Will the language benefit from it?

&:atom and &var are specifically disallowed in the initial implementation of &. But &N/:atom and &N/var will lift this restriction and may look quite befuddling in comparison to &function/arity. We will need to differentiate between &function/arity and &nargs/whatever, though their form is similar.

What if I want to ignore the first arg, but not the second? &2/func(&2)?

All in all, I don't see much benefit.

For your use case, you can make the :sources key optional and equal to nil by default. Then do dispatch to &recipe/0 if sources is nil, or to &recipe/N if sources is a non-empty list.
Best regards
Alexei Sholik

Dave Thomas

unread,
Aug 4, 2013, 11:48:29 AM8/4/13
to elixir-l...@googlegroups.com

On Sun, Aug 4, 2013 at 2:49 AM, Alexei Sholik <alcos...@gmail.com> wrote:

Support for &1/shell("%{rm} *.o") will allow one to generate an arbitrary function of N arguments that does nothing with them. Will the language benefit from it?

Yes, I believe it will, because it is orthogonal. &(...) creates a function. Right now it guesses the arity from the presence of &n in the capture. But there‘s no way to create a function of zero arity. (There’s a slightly amusing compilation error involving &0:

iex(2)> f=&[&0,&1]
** (CompileError) iex:2: capture &0 cannot be defined without &1

So at a minimum, I'd strongly support &(expression without &n) generating a zero airty function, rather that the current error(s) (compare &(42) and &(:atom)). Note that the parentheses are required here. &1 is a deprecated way of generating a function that returns its argument, and &(1)  is a proposed way of generating a zero arity function that returns 1.

Then there‘s the issue of generating functions of greater arity than would be suggested by the placeholder &ns. This is still useful (but less so that the case of fn/0, I think). I think Alex’s example is a good example. If this were to be supported, I'd suggest the syntax

&/1(expr)

If expr contains placeholders, then the highest numbered must be <= the number after the &/. Gaps would be allowed. So

&/4(&1+&3)      →   fn a, _, b, _ -> a+b end

As I said, I think this is more esoteric than the zero arity case, but it is still useful.

Dave

José Valim

unread,
Aug 4, 2013, 12:14:36 PM8/4/13
to elixir-l...@googlegroups.com

Yes, I believe it will, because it is orthogonal. &(...) creates a function. Right now it guesses the arity from the presence of &n in the capture. But there‘s no way to create a function of zero arity. (There’s a slightly amusing compilation error involving &0:

iex(2)> f=&[&0,&1]
** (CompileError) iex:2: capture &0 cannot be defined without &1

Oops, bug fixed on master. 

So at a minimum, I'd strongly support &(expression without &n) generating a zero airty function, rather that the current error(s) (compare &(42) and &(:atom)). Note that the parentheses are required here. &1 is a deprecated way of generating a function that returns its argument, and &(1)  is a proposed way of generating a zero arity function that returns 1.

Elixir makes no distinction in between &(1) and &1 at the AST level. Maybe we could support expression without &n as long as it is an explicit call but I don't think we should add it now. I would like to remove the old syntax and wait the current one get more stable before doing additions.

Then there‘s the issue of generating functions of greater arity than would be suggested by the placeholder &ns. This is still useful (but less so that the case of fn/0, I think). I think Alex’s example is a good example. If this were to be supported, I'd suggest the syntax

&/1(expr)

If expr contains placeholders, then the highest numbered must be <= the number after the &/. Gaps would be allowed. So

&/4(&1+&3)      →   fn a, _, b, _ -> a+b end
We already have a very clear syntax for expressing a function that does not use an argument: (fn -> end). We should not run away from it. They will still be plenty of cases where & can't be used. & is already walking on a very thin line regarding terseness, balancing conciseness and readability, and my feeling is that the current proposals cross it.

Dave Thomas

unread,
Aug 4, 2013, 1:25:58 PM8/4/13
to elixir-l...@googlegroups.com


>
> Elixir makes no distinction in between &(1) and &1 at the AST level. Maybe we could support expression without &n as long as it is an explicit call but I don't think we should add it now. I would like to remove the old syntax and wait the current one get more stable before doing additions.

Of course, the issue is that we use & for both the capture operator and the placeholders.  If say we were to use , $ or \ for the capture, then the issue would go away…

Anthony Grimes

unread,
Aug 4, 2013, 11:11:58 PM8/4/13
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
I akin & to Clojure's short anonymous function form, which is #(). This expands to (fn []). For arguments, you do stuff like #(%1 + %2), which should remind you of how Elixir's short form currently works. You can't do things like #(3) for a couple of reasons, one of which doesn't apply here, but one reason is that this will generate a function with zero arity which can't be passed to things like map. We've managed to get by just fine without that.

Now, one reason we've been able to get by fine is because we would normally just use partial application for something where we need to be able to pass an arg or two: (partial foo 1) = (fn [& args] (apply foo 1 args)). It's much harder (not even sure it is possible) to do generalized partial application like that, given that we don't have functions with variadic arguments and such.

In any case, even with partial application we wouldn't have been able to do what you want in Clojure. You would have had to write (fn [_] (bar 42)) yourself.

In my humble opinion, adding the &Foo.whatever(&1 ..) stuff and so on is already pushing the boundary between making things concise and making Elixir a Perl dialect. I think we should decide immediately what exactly we want & to do here and make sure it is enough. If we have to add syntax and features to it to make it useful, then frankly I'd rather do away with it entirely and just deal with having fn x -> end alone.

Dave Thomas

unread,
Aug 5, 2013, 12:49:09 AM8/5/13
to elixir-l...@googlegroups.com, José Valim

I agree to a point. But the more I think about it, the more I think the issue here is the overloading of & to mean both capture function and here's a parameter placeholder.

To me, being able to write

  Enum.map list, λ(&1+1)

just seems nicer than

  Enum.map list, fn element -> element + 1 end

Extending it to faking out arity is slightly ugly, but powerful. It could look like λ/1(42), which equates to the current fn _ -> 42 end.

OK, you say, but I don't have λ on my keyboard, to which I say “buy a bigger keyboard, or use Ukelele” :). (Or, more realistically, we could use \, which is currently unambiguous in this context. This would look like

  Enum.map list, \(&1+1)     and    \/1(42)

For the 90% use case, fn a, b -> a + b end seems noisy and verbose. &1+&2 was very nice, but clearly ambiguous and unworkable in the general case. &(&1+&2) removes some of the ambiguity, but at the cost of overloading &, which means that &(42) can't work. It also leads to constructs such as & &1, which makes my eyes tear up a little.

So I'm simply suggesting taking the next evolutionary step and changing the capture operator to \ (maybe aliased to λ, just because…), which removes the ambiguity and supports proper function capture.

Dave (channelling Mr Wall) Thomas



--

Oren Ben-Kiki

unread,
Aug 5, 2013, 12:57:38 AM8/5/13
to elixir-l...@googlegroups.com, José Valim
Haskell also uses `\`, FWIW. Except, Haskell typically uses it inside the `()`, as in `(\ x -> ... )`. `(\` looks like λ if you squint a bit. You could say `\(` is like an upside-down λ ;-)

At any rate, separate operators for separate things is a "really good idea" (I wish Elixir stuck to that rule not only here, but also in the `.` vs. `|`/`|>` vs. `|*` issue as well...)

So, +1

Oren.

On Mon, Aug 5, 2013 at 7:49 AM, Dave Thomas <da...@pragprog.com> wrote:

I agree to a point. But the more I think about it, the more I think the issue here is the overloading of & to mean both capture function and here's a parameter placeholder.

To me, being able to write

  Enum.map list, λ(&1+1)

just seems nicer than

  Enum.map list, fn element -> element + 1 end

Extending it to faking out arity is slightly ugly, but powerful. It could look like λ/1(42), which equates to the current fn _ -> 42 end.

OK, you say, but I don't have λ on my keyboard, to which I say “buy a bigger keyboard, or use Ukelele” :). (Or, more realistically, we could use \, which is currently unambiguous in this context. This would look like

  Enum.map list, \(&1+1)     and    \/1(42)

...

José Valim

unread,
Aug 5, 2013, 2:36:21 AM8/5/13
to elixir-l...@googlegroups.com
Using \ is not a good idea. Elixir is very documentation driven, including doctests, and \ would be a escape character in such. So we would either have to wrap all docs in %B""" or double escape \\.

However, I believe we could use %1 as placeholder. I have plans for % (maps) as a binary operator and that is it which means no conflicts with %1. So a proposal is:

1. Introduce %1
2. Deprecate &1, &2, etc in the old and new syntax
3. After v0.10.2 is out, make & more flexible


José Valim
Skype: jv.ptec
Founder and Lead Developer


--

Dave Thomas

unread,
Aug 5, 2013, 3:22:40 AM8/5/13
to elixir-l...@googlegroups.com

Hugs

José Valim

unread,
Aug 5, 2013, 6:16:33 AM8/5/13
to elixir-l...@googlegroups.com
An important clarification needs to be done.

Even if we change the placeholders, I don't think &42 should be allowed, nor &(42), nor &:hello. Returning a constant value from a function is an uncommon use case and those are better expressed by using the regular function syntax. The capture operator is about capturing calls and I still don't see the benefits of trying to express all constructs as part of &.

Also, another reason for not supporting function with no arity with & is because then we are unable to make a distinction in between &foo/2 meaning fn x, y -> foo(x, y) end and fn -> foo / 2 end.

So changing the placeholder is just for clarity, we won't get any extra functionality from & by doing so.


José Valim
Skype: jv.ptec
Founder and Lead Developer


Simon St.Laurent

unread,
Sep 15, 2013, 7:56:00 AM9/15/13
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
Is this change actually happening?  I don't see anything in the release notes for 0.10.2 or the documentation that suggest it did.

(I like the proposed change.)

Thanks,
Simon

José Valim

unread,
Sep 15, 2013, 11:07:28 AM9/15/13
to elixir-l...@googlegroups.com
Simon,

I don't think those changes will be introduced after all. I want to limit the number of sigils we have in the language and adding an extra sigil for & doesn't seem to be worthy it. I have tried different approaches, like unifying the sigils in the language, but they seem to add more confusion than solve issues.

José Valim
Skype: jv.ptec
Founder and Lead Developer


Reply all
Reply to author
Forward
0 new messages