Do block syntax examples

Oh, sorry, that’s what you meant, yeah that get’s closed as well. As you said the do is really syntactic sugar for passing in a function.

1 Like

Miles Lucas posted on zulip a few weeks ago a fun example of using a do-block for multi-line broadcast:

broadcast(A, B) do a, b
    q = a + b
    c = 3q - 4
    c / q
end
2 Likes

I’d like it even better if there was something like the Haskell where construct (not just for do notation), along the lines of

begin
c / q
where
q = a + b
c = 3q - 4
end

in the spirit of “show me the big picture, then the details”.

4 Likes

The following are equivalent (inside a function body):

  1. An anonymous closure

    map((x, y) -> ...body..., xs, ys)
    
  2. a do block

    map(xs, ys) do x, y
        ...body...
    end
    
  3. a function that you name

    function _f(x, y)
        ...body...
    end
    map(_f, xs, ys)
    

While I like the do block syntax, I frequently use the third option too, especially because if _f is complicated, I can just return it from the containing function for further debugging (as a closure).

4 Likes

I saw today for the first time (I’m a beginner) the use of the map () do … end block.
Trying to understand how it works, I did some tests.

The example I saw had this structure roughly:

itr=zip(1:12,'a':'l')

map(itr) do (n,c)
    c^n
end

I tried to rewrite it like this(which should be the translation in the in-line form)::

map((n,c)->c^n,itr)

but the result is different. indeed it does not seem to be a correct syntax.

to get the same result(*) I had to use two different syntax:

  1. map(c->string(c[2]^c[1]),itr)

  2. map((n,c)->c^n, collect(1:12),collect('a':'l'))

therefore the two forms are not always "directly and simply " exchangeable?!?!?

(*)

12-element Array{String,1}:
 "a"
 "bb"
 "ccc"
 "dddd"
 "eeeee"
 ⋮
 "hhhhhhhh"
 "iiiiiiiii"
 "jjjjjjjjjj"
 "kkkkkkkkkkk"
 "llllllllllll"
1 Like

You want map(((n,c),)->c^n,itr) to destructure the tuples obtained by iterating over a zip.

4 Likes

I like Python’s version much better. It has an as keyword which is more intuitive to me.

It’s interesting to see how much preferences can vary here. I really enjoy the Julia/Ruby/Kotlin/Scala/Elixir model of exposing a lambda as a block instead of having dedicated context managers. Python context managers are inflexible and require a lot of boilerplate for custom implementations. with blocks also don’t create new scope and thus pollute the current scope with more names that you can’t use for other variables.

5 Likes

Then this means that the do … end block does this destructuring operation automatically (whatever that means :grinning:)?

It’s not specific to the do block. Maybe it’s clearer when looking at a function that has both normal and destructured arguments:

function f((a,b), c)     # Function of 2 arguments
    a+b+c
end

tup = (1,2)

f(tup, 3)  # result: 6

An anonymous function doing the same would be ((a,b), c) -> a+b+c.

With a do block it’s the same. Instead of map(f, [tup, tup, tup], [3, 4, 5]) you can write

map([tup, tup, tup], [3, 4, 5]) do (a,b), c
    a+b+c
end

The only difference is that the do arguments are not written in parentheses, so do a, b declares a function of two arguments while do (a, b) declares a function of one argument, which is then destructed in two variables a and b. This is admittedly not obvious when looking at the do (a, b) syntax in isolation :slight_smile:

6 Likes

Since the thread has been revived, here’s an example where I find the do syntax particularly intuitive:

julia> using Makie

julia> scene = Scene()

julia> on(scene.events.mouseposition) do pos
           println("Mouse is at $pos")
       end
3 Likes

I see the point.
tanks.

I ran into a situation where I tried to brodcast on a vector of tuples and ran into difficulties, which I got over.
But I would like to understand better, how things are going.

I wanted to check if a given tuple was contained in a list of tuples and I used a similar to this expression which gave me error:

    julia> (2,3) .==[(2,3),(1,1),(5,4)]
    ERROR: DimensionMismatch("arrays could not be broadcast to a common size; got a dimension with lengths 2 and 3")

I solved it by changing it like this:

     ((2,3),) .==[(2,3),(1,1),(5,4)]

I would like to understand how it works instead in the following case:

julia> (2,) .== [(2,),3,4,2,5]
5-element BitArray{1}:
0
0
0
1
0

That’s just how broadcasting works - the tuple (2,3) on the left hand side is iterable, so broadcasting will try to iterate over its contents. You are trying to do

[2 == (2,3), 3 == (1,1), ⋆ == (5,4)]

where here denotes the missing element on the left hand side that the error complains about (2 elements in the tuple on the LHS, 3 elements in your vector of tuples on the RHS).

Wrapping the LHS in a tuple is one way of doing this, another would be to use Ref to make clear that the LHS is to be treated as a scalar:

julia> Ref((2, 3)) .== [(2,3), (1,1), (5,4)]
3-element BitArray{1}:
 1
 0
 0
4 Likes

i tried this but without success:smile:

 julia> do (2,3) end .==[(2,3),(1,1),(5,4)]
 ERROR: syntax: invalid "do" syntax

What are you trying to do here?

An equivalent approach to the broadcast would be:

map([(2,3),(1,1),(5,4)]) do elem
    elem == (2, 3)
end

The do syntax is to be used after some method call that takes a function/closure/callable as first parameter. You ommit the first parameter (the callable) and instead define you a closure with the parameters after the do (in the same line) and the body of the closure in the following lines until the end.

3 Likes

Ok guys this confused the hell out of me until I saw the official do keyword documentation: Essentials · The Julia Language

Basically, here’s what do does:

map(1:10, 11:20) do x, y
    x + y
end

is the same as:

map((x,y)->(x+y), 1:10, 11:20)

So anywhere that a function takes another function as a first argument, it is valid to use a do block to define what that first argument function is.