Skip to content

Instantly share code, notes, and snippets.

@samuelgoto
Last active November 29, 2017 22:24
Show Gist options
  • Save samuelgoto/84b6f34be7e64a83cf0dd3282d3a4d1a to your computer and use it in GitHub Desktop.
Save samuelgoto/84b6f34be7e64a83cf0dd3282d3a4d1a to your computer and use it in GitHub Desktop.

Heavily inspired by kotlin and groovy, what would the following syntax simplication enable?

a {}

// syntact sugar for

a(function() {});

?

@samuelgoto
Copy link
Author

samuelgoto commented Oct 19, 2017

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);
}

FAQ

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;
  }
}

Annex

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");

labels

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?

@samuelgoto
Copy link
Author

@samuelgoto
Copy link
Author

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 ...
  }
}

@samuelgoto
Copy link
Author

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"
   

@samuelgoto
Copy link
Author

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()

@samuelgoto
Copy link
Author

samuelgoto commented Oct 30, 2017

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
    }
  }
}

@samuelgoto
Copy link
Author

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")
    }
  }
}

@samuelgoto
Copy link
Author

samuelgoto commented Nov 29, 2017

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;
  }
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment