redpanda-data / connect

Fancy stream processing made operationally mundane
https://docs.redpanda.com/redpanda-connect/about/
8.13k stars 834 forks source link

Bloblang root level `if` statements #2115

Closed Jeffail closed 5 months ago

Jeffail commented 1 year ago

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:

  1. We need to ensure that we don't collide with if <foo> { <bar> } being shorthand for root = if <foo> { <bar> }.
  2. We need to decide which assignment types are allowed within these new expressions, should variable assignments go in there? What about map definitions?
erhhung commented 9 months ago

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.

mihaitodor commented 5 months ago

Added in v4.26.0.