Why for loops don't return the last evaluated expression as their value?


#1

I was wondering why for loops don’t return the last evaluated expression – similar to if statements, for example.


#2

This is just speculation, but perhaps it’s because it’s not clear what to return when the iterator is empty? That is, if you wrote:

y = for i in 1:N 
  i + 1
end

then what should y be if N < 1 (in which case the body of the loop is never executed)? We could decide that nothing is a sensible default, but then that would make assigning the result of the loop inherently type-unstable, since the returned type (Int or Void) would depend on the value of N.


#3

Correct, it’s an inherent type instability. See also:

https://github.com/JuliaLang/julia/pull/23260

This PR implements break-with-value and for with else, which together mean that it actually does make sense for a loop to return a value. Still a speculative feature, but we’ve got an implementation and may add this feature in the future (1.x).


#4

Thanks

@rdeits How would that be any different from

z = if false
  "moo"
end

Only through the fact that one can optionally add an else as a safeguard against type instability?

@StefanKarpinski Can’t remember seeing these constructs in any other languages that I’ve used, but I find them super-cool. Very useful syntax innovations, I’d love to see them available.


#5

That’s a good point – it’s not really any different. I suppose we could make loops return the last value evaluated in the loop. It might be annoying in the REPL when you write loops.


#6

That would certainly make the behavior consistent. But to be truly useful, in the absence of more exotic for-else or break-with-value constructs, break would also have to return the last evaluated expression. That’s how I stumbled into this actually, trying to do something in the line of:

_path, _extension = for file_extension in [".html", ".md", ".mt"]
  if isfile(file_path * file_extension)
    file_path * file_extension, file_extension
    break
  end
end

To me it looks like a legit usage of the language, something I definitely expected to work and it took me quite a bit to understand the error I was getting.


#7

Looks like this might be a good fit for the API consistency review issue.


#8

I can certainly see the appeal — I occasionally think about syntactic extensions that would make sense in a given context, but at the same time I admire the restraint in the design of Julia that kept complexity and clever variants of syntax with subtly different semantics to a minimum.

Is

result = some_default
for elt in itr
    ...
end
result

that inconvenient? I find it to be a good trade-off between readability and compactness.

I could also imagine a Lisp-like

@prog1 result = some_default for elt in itr
    ...
end

but again, I don’t think it is worth it.


#9

You could do it like in Scala where if you want the for to return a value you need a yield statement inside the loop.


#10

Yes, that’s what the PR I linked to above does.