Heavily inspired by kotlin and groovy, what would the following syntax simplication enable?
a {}
// syntact sugar for
a(function() {});
?
https://www.safaribooksonline.com/library/view/the-ruby-programming/9780596516178/ch08s08.html
after (100) {
// run just once after 100ms
}
every (100) {
// run forever every 100ms
}
https://hugotunius.se/2014/08/19/custom-control-structures-in-swift.html
Swift has a feature called @autoclosure
which wraps any expression passed in a closure.
func _while(condition: @autoclosure () -> BooleanType, action: () -> ()) {
while condition() {
action()
}
}
var i = 0
_while(i < 10) {
println("\(i)")
i += 1
}
Good discussion on reddit.
continue
and break
break
can break out of any block. For example:
foo: { a = 1; break foo; a = 2 }
console.log(a) // 1
Apparently, you need to be lexically nested to break
to labels. For example:
foo: while(true) { console.log("hi"); break foo; }
console.log("left foo")
bar: while(true) { console.log("hey"); break foo; } // throws "undefined label foo"
console.log("left bar")
Works with if
:
foo: if (true) {
console.log("this gets executed");
break foo;
console.log("this does not");
}
console.log("this does too")
Whether it is a modifier that gets used in the call site (e.g. for each(array) {}
) or at the declaration site (e.g. ```loop function foreach() { ... }). For example, you used a modifier in the function call:
loop function a(expr, block) {
}
a(true) {
...
break;
...
continue;
...
}
It gets desugared to:
__exit__: a() {
__block__: do {
...
// all "breaks" here are replaced for "break __exit__"
break __exit__;
...
// all "continues" here are replaced for "continue __block__"
continue __block__;
...
} while (false);
}
For example:
loop function foreach(array, block) {
for (item of array) {
block.call(item);
}
}
foreach (item in [0, 1, 2, 3]) {
if (item < 2) {
continue;
} else {
break;
}
}
For example:
// a label gets added automatically at the top of the statement
__exit__:
foreach (item in [0, 1, 2, 3]) {
// a labelled block gets added automatically wrapping the block param.
__block__: do {
if (item < 2) {
// continue leaves the __block__
continue __block__;
} else {
// arg-less break gets desugared into breaking to the head of the function.
break __exit__;
}
} while (false);
}
What happens with nested block params? E.g.
foreach (let item in array) {
foreach (let other in item.array) {
// will break and continue do the right things here?
if (other.i == 1) {
break;
}
continue;
}
}
continue
and break
can take a label as a parameter:
foo: while (true) {
console.log("this gets executed");
break foo;
console.log("this never gets executed");
}
console.log("this gets followed");
foreach(let item in iterable) {
if (item == 0) {
break end;
} else {
continue foreach;
}
}
end:
but if break and continue from "loops" such as arr.forEach are rare enough, why not use a real label for labeling the exit point?
Really great considetations here!
http://wirfs-brock.com/allen/files/jshistory/continue-break-lambda.pdf
This only works for statements (so, not allowed inside expressions), but ...
function unless(block) {
if (!expr) {
return block();
}
}
while (true) {
unless (false) {
break;
}
}
Could desugar to:
while (true) {
{
let result = unless (false, function() {
return {break: true};
});
if (result.break) {
break;
} else if (result.continue) {
break;
} else if (result.return){
return result.value;
}
// otherwise, just carry on ...
}
}
Here is how ruby deals with these challenges:
def foreach(list)
puts "Running foreach on #{list}"
# you can call the block using the yield keyword
for i in list
puts yield(i)
end
puts "End of method"
end
puts ""
puts ""
puts "Sample #1: basic usage of blocks"
puts ""
puts ""
foreach (0..2) {
# This is how you declare arguments to the block
|value|
puts "Hi from the block with args: #{value}"
'A return value!'
}
puts ""
puts ""
puts "Sample #2: breaks inside blocks"
puts ""
puts ""
# breaks just return from the block and return null.
result = foreach (2..3) {
puts "Hi from block with break!"
break
}
puts "Result is nil, right? #{result == nil}."
puts ""
puts ""
puts "Sample #3: breaks inside blocks with for-loops nested"
puts ""
puts ""
# For example, break inside a lambda doesn't leave the
# outer lambda:
for i in 0..2
puts "Iterating: #{i}!"
foreach (0..i) {
|value|
puts "Iterating #{value}"
# This only leaves the inner foreach, not the outer for
break
}
end
puts ""
puts ""
puts "Sample #4: what does continue do?"
puts ""
puts ""
# For example, break inside a lambda doesn't leave the
# outer lambda:
for i in 0..2
puts "Iterating: #{i}!"
foreach (0..i) {
|value|
puts "Iterating #{value}"
# This only leaves the inner foreach, not the outer for
next
}
end
puts ""
puts ""
puts "Sample #5: what does return do?"
puts ""
puts ""
def foo
foreach (0..0) {
return "hello world"
}
return "not this"
end
# Returns "hello world" rather than "not this".
foo()
And console:
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]
Sample #1: basic usage of blocks
Running foreach on 0..2
Hi from the block with args: 0
A return value!
Hi from the block with args: 1
A return value!
Hi from the block with args: 2
A return value!
End of method
Sample #2: breaks inside blocks
Running foreach on 2..3
Hi from block with break!
Result is nil, right? true.
Sample #3: breaks inside blocks with for-loops nested
Iterating: 0!
Running foreach on 0..0
Iterating 0
Iterating: 1!
Running foreach on 0..1
Iterating 0
Iterating: 2!
Running foreach on 0..2
Iterating 0
Sample #4: what does continue do?
Iterating: 0!
Running foreach on 0..0
Iterating 0
End of method
Iterating: 1!
Running foreach on 0..1
Iterating 0
Iterating 1
End of method
Iterating: 2!
Running foreach on 0..2
Iterating 0
Iterating 1
Iterating 2
End of method
Sample #5: what does return do?
Running foreach on 0..0
=> "hello world"
Another example in Ruby:
def iffy(condition)
if (condition) then
yield()
end
end
iffy (true) {
puts "This gets executed!"
}
iffy (false) {
puts "This does not"
}
for i in 0..1
puts "Running: #{i}"
iffy (i == 0) {
# This does not break from the outer loop!
# Prints
#
# Running: 0
# Running: 1
break
}
end
for i in 0..1
iffy (i == 0) {
# This does not continue from the outer loop!
# Prints
#
# Running: 0
# Running: 1
next
}
puts "Running: #{i}"
end
def foo()
iffy (false) {
return "never executed"
}
iffy (true) {
return "executed!"
}
return "blargh, never got here!"
end
# Prints "executed!"
foo()
Kotlin disallows break
and continue
inside blocks:
fun unless(condition: Boolean, block: () -> Unit) {
if (condition) {
block()
}
}
fun main(args: Array<String>) {
println("Hello, world!")
while (true) {
unless(true) {
println("hello")
// Error:(11, 12) 'break' or 'continue' jumps
// across a function or a class boundary
// break
// 'return' is not allowed here
// return
}
}
}
fun case(condition: Boolean, block: () -> Unit) {
println("This gets called!")
block()
}
fun select(condition: Boolean, block: () -> Unit) {
block()
}
fun main(args: Array<String>) {
var expr: Boolean = true;
select (expr) {
case (true) {
println("Totally true")
}
case (true) {
println("Totally false")
}
}
}
Interesting use case in constructors for Ruby:
https://mixandgo.com/blog/mastering-ruby-blocks-in-less-than-5-minutes
let car = new Car() {
::color = 1;
::size = "large";
}
Could that lead to something like abstract classes?
sort([2, 3, 1, 4], new Comparator() {
::compare do (a, b) {
return a < b;
}
})
Principle of Abstraction
Tennents Correspondence Principle
These lambdas are particularly interesting. With arrow functions, we went as far as disabling
break
,continue
andyield
from top level statements, but we leftreturn
.In this formulation, there are a few more cases that break the principle:
return
.this
isn't lexical.var
? should we disallow it and force the block-scopedlet
?throws
should probably be supported.TODO(goto): figure out how kotlin is planning to introduce
break
andcontinue
into the closures.http://yehudakatz.com/2012/01/10/javascript-needs-blocks/
https://web.archive.org/web/20161123223104/http://wiki.ecmascript.org/doku.php?id=strawman:block_lambda_revival
return
returns from the outer scope rather than the inner one.