Bloblang root level `if` statements
Bloblang is pretty cool, and a lot of that coolness is that everything is an expression and mutations are methods you can chain:
map remove_naughty_man {
root = match {
this.type() == "object" => this.map_each(item -> item.value.apply("remove_naughty_man")),
this.type() == "array" => this.map_each(ele -> ele.apply("remove_naughty_man")),
this.type() == "string" => if this.lowercase().contains("voldemort") { deleted() },
this.type() == "bytes" => if this.lowercase().contains("voldemort") { deleted() },
_ => this,
}
}
root = this.apply("remove_naughty_man")
Including error handling and flow control. However, we're also imperative as assignments work as a series of isolated mapping statements:
root.this = "is totally"
root.imperative = "statements"
Currently if a map author wants to use a conditional expression to toggle a series of imperative assignments they need to refactor their assignments into a single one:
root = if this.foo == "huh" {
root.merge({
"this": "is totally",
"not": "imperative",
})
}
Ultimately the statements acting as expressions is the more useful paradigm and I believe it was right to emphasise solving problems this way as it's much more powerful. Traditionally, users that want a truly imperative scripting/mapping environment have lots of options within Benthos such as the javascript and awk processors.
However, now that the language is maturing and has been stable for a long time we should consider adding if-expressions that act upon root level imperative assignments. There are some things to consider:
- We need to ensure that we don't collide with
if <foo> { <bar> }being shorthand forroot = if <foo> { <bar> }. - We need to decide which assignment types are allowed within these new expressions, should variable assignments go in there? What about map definitions?
I would second having support for imperative match as well as if statements so that we could have "switch" statements like this:
root = this
match this.status {
"pending" => {
root.status = "confirmed"
root.confirmed_at = now()
},
"canceled" => {
if random_int() % 2 == 0 {
root.canceled_at = now()
} else {
root = deleted()
}
}
}
Although, possible "gotchas" for this match statement form would be the new this context in the "case" blocks.
Added in v4.26.0.