inko
inko copied to clipboard
Consider re-introducing pattern matching in `let` through syntax sugar
Description
At some point Inko supported pattern matching in let
, meaning you could write e.g. let Some(val) = some_option else return
or let (a, b) = c
. This was removed due to the complexity of the implementation, as it wasn't able to reuse the same compilation steps for match
expressions, instead having to re-implement/copy portions of it.
I would like to re-introduce this in a limited form, as not having it can result in deeply nested code. For example, now you end up with this:
@state.update_with(updates) fn move (rooms, updates) {
updates.into_iter.each fn (entry) {
match entry {
case { @key = name, @value = status } -> {
let room = rooms.get_mut(name)
let update = match room.status {
case Humid or Button -> false
case old -> {
status >= old or room.last_update.elapsed.to_nanos >= wait
}
}
if update { room.update(status) }
}
}
}
}
Instead of something like this (which is nicer):
@state.update_with(updates) fn move (rooms, updates) {
updates.into_iter.each fn (entry) {
let { @key = name, @value = status } = entry
let room = rooms.get_mut(name)
let update = match room.status {
case Humid or Button -> false
case old -> {
status >= old or room.last_update.elapsed.to_nanos >= wait
}
}
if update { room.update(status) }
}
}
As for the implementation: we should implement this as a desugaring pass that operates on HIR, before type-checking. Essentially for every let PATTERN = VALUE
we encounter, we turn that into match VALUE { case PATTERN -> after }
where after
is all the code that follows the let
expression. Thus this:
let (a, b) = c
foo
Becomes this:
match c {
case (a, b) -> {
foo
}
}
Re-introducing let ... else
would complicate matters, as the expression of else
must return/break from the surrounding scope, as the code that comes after can't run without the pattern matching. Part of the complexity of the past implementation involved handling that, generating the correct code, type-checking, etc. As such I'm inclined to just not re-introducing that, and directing people to just using match
for such needs. Given the two most common cases are destructuring tuples and classes (and not e.g. enums), I think not supporting else
should be fine.
With that all said, I find myself mostly needing this when iterating over a Map
and wanting to break the Entry
values down into separate variables. Outside of that I haven't had a real need for this, so I'm not yet convinced this is actually worth the maintenance overhead and compiler complexity.
Related work
No response
Another option is to solve this at the standard library. Types such as Entry
, Option
, Result
, etc could provide a let
method that yields in the OK/some/etc case, passing its components as separate arguments. For example, for Entry
you'd then write something like this:
map.into_iter.each fn (entry) {
entry.let fn (key, value) {
...
}
}
You'd still have an extra level of indentation, though at least we wouldn't have to further complicate the compiler.
Kotlin has a somewhat similar feature to the let
method described above: https://kotlinlang.org/docs/scope-functions.html#let