Heavily inspired by kotlin and groovy, what would the following syntax simplication enable?
a {}
// syntact sugar for
a(function() {});
?
configuration files
https://github.com/helpermethod/membrane-next
@Grab
import static com.github.helpermethod.membrane.dsl.Membrane.router
router {
serviceProxy('names') {
filter {
port 2000
pathRegex '/(rest)?names.*'
}
interceptors {
rewriter {
map '^/names/(.*)', '/restnames/name\\.groovy\\?name=$1'
}
}
target {
host 'thomas-bayer.com'
port 2000
}
}
}
would it be possible to aid on the Map and Set thingies, e.g.
let a = new Map() {
1 = "hi"
2 = "hey"
foo = "bar"
}
kotlin gradle dls:
https://rodm.github.io/blog/2017/04/teamcity-kotlin-dsl.html
buildscript {
repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.3'
classpath 'com.github.rodm:gradle-teamcity-plugin:0.11'
}
}
asserts:
https://artemzin.com/blog/ui-testing-separating-assertions-from-actions-with-kotlin-dsl/
@Test fun loginAndPasswordAreEntered() {
loginScreen {
login("artem_zin")
password("*****")
assert {
loginButtonActivated()
noLoginWarnings()
noPasswordWarnings()
}
}
}
Testing DSL:
given("a calculator", {
val calculator = Calculator()
on("calling sum with two numbers", {
val sum = calculator.sum(2, 3)
it("should return the sum of the two numbers", {
shouldEqual(5, sum)
}
}
}
This scoping:
class A { b() { console.log(this) } }
> undefined
with (new A()) { b() }
> VM230:1 A {}
var p = new Proxy({}, {
get: function(target, name) {
return `hello world ${name}`;
}
});
> undefined
> p.a
> "hello world a"
with (p) { console.log(a) }
> Uncaught ReferenceError: a is not defined
Why?
NOTE(goto): how do you break from these loops? do you need to break() which throws an exception?
${key()}: ${value()}
); }builders:
survey("TC39 Meeting Schedule") {
question("Where should we host the European meeting?") {
option("Paris")
option("Barcelona")
option("London")
}
}
humm i'm wondering if it is absolutely necessary to have the requirement to change all of the calls to include this
. i'm wondering if we pass things as function parameters, just basic parameter resolution would suffice. For example:
function a(b, c) {
b()
c()
}
// the problem is that the lambda block doesn't have any parameter names
// declared ..
a {
// b was never declared as a parameter of the function ...
}
open("filename.txt", "r") {
// uses the file ...
}
languages that deliberately add support for DSLs:
In this formulation, the expansion would implicitly include the this
binding. So, a { ... }
would be equivalent to a.call(function { ... })
.
let html = div {
span("hello world") {}
}
this
method resolutionIn this formulation, the resolution of methods looks first for the presence in the this
object for function calls before looking at the local scope and later at the global scope. e.g. a { b() }
is equivalent to ```a(function() { (b in this ? this.b : b)() }).
For example:
let html = div {
// "span" would first be looked at 'this' before looking at the global scope
span {
}
}
This may be isomorphic to the equivalency a { b() }
to a(function() { with (this) { b() } })
In this formulation, the expansion would be simply a { ... }
to a(function() { ... })
and this
would be passed via the bind operator
let html = div {
::div {
::span {
::p("hello world")
}
}
}
In this formulation, we would pick a special syntax space to make the distinction between the this
binding and regular function calls.
let html = <div> {
<div> {
<span> {
<p>("hello world")
}
}
}
This can open a stream of future extensions that would enable further constructs to be added. Here are some that occurred to us while developing this.
To enable something like if (arg1) { ... } else if (arg2) { ... } else { ... }
you'd have to chain the various things together. @erights proposed something along the lines of making the chains be passed as parameters to the first function. So, that would transpile to something like if(arg1, function() { ... }, "else if", arg2, function { ... }, "else", function () { ... })
.
Another notable example may be to enable try { ... } catch (e) { ... } finally { ... }
To enable control structures that repeat over the lambda (e.g. for-loops), we would need to re-execute the stop condition. Something along the lines of:
repeat { ... } until ( expr )
we would want to turn expr
into a function that evaluates expr
so that it could be re-evaluated multiple times. For example repeat { ... } until (() => expr)
.
TODO(goto): should we do that by default with all parameters?
There are a variety of cases where binding helps. Currently, we pass parameters back to the block via this
. For example, we would want to enable something like the following:
foreach ({key, value} in map) { ... }
to be given by the forach function implementation.
Tennents Correspondence Principle
These lambdas are particularly interesting. With arrow functions, we went as far as disabling break
, continue
and yield
from top level statements, but we left return
.
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-scoped let
?throws
should probably be supported.
TODO(goto): figure out how kotlin is planning to introduce break
and continue
into the closures.
http://yehudakatz.com/2012/01/10/javascript-needs-blocks/
In order to have a language with return (and possibly super and other similar keywords) that satisfies the correspondence principle, the language must, like Ruby and Smalltalk before it, have a function lambda and a block lambda. Keywords like return always return from the function lambda, even inside of block lambdas nested inside. At first glance, this appears a bit inelegant, and language partisans often accuse Ruby of unnecessarily having two types of "callables", in my experience as an author of large libraries in both Ruby and JavaScript, it results in more elegant abstractions in the end.
In contrast, when functions are used as callbacks, those keywords no longer make sense. What does it mean to return from a function that has already returned?
return
returns from the outer scope rather than the inner one.
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;
}
})
jenkins job dsls