alternative do-block syntax proposal

534 views
Skip to first unread message

Tomek Trzeciak

unread,
Jul 25, 2014, 8:13:31 AM7/25/14
to juli...@googlegroups.com
When I first read about the do syntax in Julia I thought it's a neat sugar, but then it seemed a bit too magic to me. What I mean by magic is that
map([A, B, C]) do x
   
if x < 0 && iseven(x)
       
return 0
   
elseif x == 0
       
return 1
   
else
       
return x
   
end
end
this doesn't give any clue to the unversed that the do-block will create an anonymous function and pass it as the first argument to the function call preceding the block. As an alternative consider this:
map((x)->..., [A, B, C]) 
begin
    if x < 0 && iseven(x)
        return 0
    elseif x == 0
        return 1
    else
        return x
    end
end
I think this makes it much easier to guess the meaning of the construct, plus it has the benefit that in this way one could also allow to pass the anonymous as any other argument if desired, not just the first one:
somefunc(A, B, (x,y)->...)
begin
return y, x
end
Note also that the begin keyword doesn't need to be on the same line as the function call as with the do-construct, since there should be no ambiguity in parsing this (the syntax ->... makes it clear that we need to complete the function definition). In principle, one could allow for any scoping block (for, while, let) to be used there, not just begin-end.

Going with this even further, multiple anonymous function arguments could be allowed with each ->... meaning sort of a deferred definition with the next N scoping blocks after the function call implementing the body of each anonymous function in turn, although this might be stretching it too far.

Anyway, I wonder what are your thoughts on this?

Jameson Nash

unread,
Jul 25, 2014, 1:27:39 PM7/25/14
to juli...@googlegroups.com
This sounds like it could be useful for implementing an alternative to the do/else proposal. I also like how it makes the anonymous function creation a bit more obvious. 

Simon Danisch

unread,
Jul 26, 2014, 10:09:59 AM7/26/14
to juli...@googlegroups.com
Good idea!
I never felt at ease with the do-block syntax...

Lucas Garron

unread,
Jul 26, 2014, 4:09:37 PM7/26/14
to juli...@googlegroups.com
While the ... looks kind of cool, its role feels unnecessarily context-sensitive. What about specifying that we're using a locally created function, but without introducing new syntax inside the map call? The first thing that comes to mind is a Haskell-like where clause:

map(f, [A, B, C]) where f = x->begin
   if x < 0 && iseven(x)
       return 0
   elseif x == 0
       return 1
   else
       return x
   end
end

This resembles the original example very closely.

(You could also expand it to handle multiple local functions, but at that point you'd probably want to take a closer look at how other languages have handled where/let/etc.)

--------

Removing context-sensitivity might expose you to a few more bugs: if you accidentally change your code to:

f x = #already defined
map(f, [A, B, C]) where betterNamedF = (x->begin

then it may still compile and run. If f and betterNamedF are similar, you might not even catch the bug until much later.
I can think of two counter-measures, though:

- Warn the user that betterNamedF wasn't used (this is simpler than looking for unused variables in general, since the valid scope is small and bounded).
- Use a style convention for the name of f, so that incorrect definitions and uses stand out (and a linter could catch it?).






»Lucas Garron

Ethan Anderes

unread,
Jul 27, 2014, 12:01:59 AM7/27/14
to juli...@googlegroups.com
+1. I don't know Haskell, so that syntax is new to me, but I instantaneously love it.

Alireza Nejati

unread,
Jul 27, 2014, 7:21:41 PM7/27/14
to juli...@googlegroups.com
That syntax loses anonymity.

You can just as easily define f separately and pass it to the function; no 'where' clause needed.

Tomas Lycken

unread,
Jul 29, 2014, 4:42:54 AM7/29/14
to juli...@googlegroups.com

I like the alternative syntax! +1 =)

You can just as easily define f separately and pass it to the function; no ‘where’ clause needed.

Yes, of course. That is still an option for map, open et al, but the new syntax still has the advantage of making it very clear exactly where the function is going to be used. I find that when I use anonymous functions in various contexts, it’s usually because I know I won’t be re-using the function anywhere else, and therefore I don’t want to define anything that clutters up the name space. The proposed syntax (both the `(x,y)->...` variant and the `where` clause) still solve that problem, just as well as the current syntax with do blocks.

// T

Mike Innes

unread,
Jul 29, 2014, 5:23:29 AM7/29/14
to juli...@googlegroups.com
Since no one else has, I'll chime in in support of the current do block. If anything the proposal seems less intuitive/elegant to me.

The where block is nicer but a new keyword/syntax is going to need a pretty compelling reason to exist, and in this case you can simply write:

function f(x)
  # code...
end
map(f, 1:10)

Within a lexical scope (let block, function body, whatever) this will create an anonymous function rather than cluttering the global namespace. It also has the advantage that you can use multiple dispatch:

function test(xs)
  f(x::Int) = :int
  f(x::Real) = :real
  map(f, xs)
end

This is more flexible and IMO clearer, so long story short I think a major change in this area will be unlikely.

Stefan Karpinski

unread,
Jul 29, 2014, 10:45:44 AM7/29/14
to Julia Dev
To me the major driver for some variation on the current syntax is wanting to chain calls via the |> operator or something like it. I.e. something like this:

v |> f |> 1+_ |> sort(_, rev=true)

This is somewhat related to the idea of having an even terser anonymous function syntax.

Ethan Anderes

unread,
Jul 29, 2014, 11:30:18 AM7/29/14
to juli...@googlegroups.com

That is a cool suggestion. Would you also be able to do something like this

remotecall(2, _, args...) do x
 # blah
end

If so, it would accomplish what the where syntax was trying to do.

Stefan Karpinski

unread,
Jul 29, 2014, 11:46:32 AM7/29/14
to Julia Dev
So the problem with that syntax is that it's ambiguous how much of the expression _ appears in is part of the implied anonymous function. Scala uses the _ identifier like this and resolves that ambiguity with some imo crazy feedback from the type checker – iirc, when an _ appears like this (it has like a dozen different meanings in Scala), it the expression it turns into an anonymous function is the smallest one that is used in a context that expects a function value. As a result you need to run type inference and the type checker to parse Scala; this is why it's nuts to do this. Swift takes a much saner approach: it uses $1, $2, etc. for variables, and the extent of the anonymous function is explicitly delimited by curly braces.

We don't have the curly brace syntax available in Julia, so that's not an option. There's no way we're going to get types involved in parsing, so that's also not happening. What we need is a simple, purely syntactic rule for how much of the expression surrounding _ becomes an anonymous function. I've never come up with a satisfactory one.

Tomek Trzeciak

unread,
Jul 29, 2014, 1:37:22 PM7/29/14
to juli...@googlegroups.com


On Tuesday, July 29, 2014 10:23:29 AM UTC+1, Mike Innes wrote:
Since no one else has, I'll chime in in support of the current do block. If anything the proposal seems less intuitive/elegant to me.

The where block is nicer but a new keyword/syntax is going to need a pretty compelling reason to exist, and in this case you can simply write:

function f(x)
  # code...
end
map(f, 1:10)

Within a lexical scope (let block, function body, whatever) this will create an anonymous function rather than cluttering the global namespace. It also has the advantage that you can use multiple dispatch:

function test(xs)
  f(x::Int) = :int
  f(x::Real) = :real
  map(f, xs)
end

This is more flexible and IMO clearer, so long story short I think a major change in this area will be unlikely.

I agree wit you that some this:

let
 
local f
 
function f(x::Int)
   
return x*x
 
end
  map
(f, [1 2 3])
end

is indeed quite clear, flexible and avoids cluttering the current scope (although somewhat more verbose, but still not too bad I'd say). Hmm, this makes me think that do could be done away with in the language entirely ;)

Tomek Trzeciak

unread,
Jul 29, 2014, 1:49:03 PM7/29/14
to juli...@googlegroups.com


On Tuesday, July 29, 2014 3:45:44 PM UTC+1, Stefan Karpinski wrote:
To me the major driver for some variation on the current syntax is wanting to chain calls via the |> operator or something like it. I.e. something like this:

v |> f |> 1+_ |> sort(_, rev=true)

This is somewhat related to the idea of having an even terser anonymous function syntax.

How about:

v |> f(...) |> 1+... |> sort(..., rev=true)

perhaps with the implied trialing (...) if not given explicitly, so one could write f instead of f(...)? What give me pause, though, is that this seems too much like a line noise (obfuscation contest in Julia, anyone? ;).

Stefan Karpinski

unread,
Jul 29, 2014, 2:26:55 PM7/29/14
to Julia Dev
Changing _ to ... doesn't really solve the problem (actually, it's more problematic since ... already has two (closely related) meanings.

Mike Innes

unread,
Jul 29, 2014, 2:52:10 PM7/29/14
to juli...@googlegroups.com
What do you think of the @as macro in Lazy.jl? It lets you write expressions like

@as _ v f (1+_) sort(_, rev=true)

For my money this kind of syntax is definitely useful, but not so common that it needs to be built into the parser – a macro seems like a good fit.

Kevin Squire

unread,
Jul 29, 2014, 3:04:20 PM7/29/14
to juli...@googlegroups.com
I like the functionality (and was part of the earlier discussion), but find it hard to read without the `|>` separator (which is obviously extraneous, but a useful visual cue).

Kevin

Stefan Karpinski

unread,
Jul 29, 2014, 3:13:03 PM7/29/14
to Julia Dev
I agree with Kevin. I also think that if it was better supported, it might become useful enough to warrant better support ;-)

Jeff Bezanson

unread,
Jul 29, 2014, 5:32:47 PM7/29/14
to juli...@googlegroups.com
I would be in favor of making |> built-in syntax for something like
@as, and handling the few existing method definitions for |> by
replacing them with functions that return closures.

James Porter

unread,
Aug 1, 2014, 12:55:53 AM8/1/14
to juli...@googlegroups.com
bumping my old PR for @as from an earlier round of this discussion :) https://github.com/JuliaLang/julia/pull/5734

I would be in favor of making |> do something like this automagically though.
Reply all
Reply to author
Forward
0 new messages