Desugaring ?

What's syntax sugar?

Syntax within a programming language that is designed to make things easier to read or express. It allows developers to write code in a more concise, readable, or convenient way without adding new functionality to the language itself.

Desugaring converts syntax sugar (like x + 1) into more fundamental operations (like Num.add(x, 1)).

Let's see how ? is desugared. In this example we will extract the name and birth year from a string like "Alice was born in 1990".


parse_name_and_year : Str -> Try({ name : Str, birth_year : U16 }, [BadFormat, BadNumStr, ..])
parse_name_and_year = |str| {
	{ before: name, after: birth_year_str } = str.split_first(" was born in ") ? |_| BadFormat
	birth_year = U16.from_str(birth_year_str)?
	Ok({ name, birth_year })
}

After desugaring, this becomes:


parse_name_and_year_try : Str -> Try({ name : Str, birth_year : U16 }, [BadFormat, BadNumStr, ..])
parse_name_and_year_try = |str| {
	match str.split_first(" was born in ") {
		Err(_) => Err(BadFormat)
		Ok({ before: name, after: birth_year_str }) => {
			match U16.from_str(birth_year_str) {
				Err(err2) => Err(err2)
				Ok(birth_year) => Ok({ name, birth_year })
			}
		}
	}
}

So birth_year = U16.from_str(birth_year_str)? is converted to

match U16.from_str(birth_year_str) {
    Err(err2) => Err(err2),
    Ok(birth_year) => birth_year,
}

As you can see, the first version is a lot nicer!

Thanks to ?, you can write code in a familiar way and you get the benefits of Roc's error handling to drastically reduce the likelihood of crashes.

Full Code

main! = |_| {
	echo!("${Str.inspect(parse_name_and_year("Alice was born in 1990"))}\n")
	echo!("${Str.inspect(parse_name_and_year_try("Alice was born in 1990"))}\n")
	Ok({})
}

### start snippet question
parse_name_and_year : Str -> Try({ name : Str, birth_year : U16 }, [BadFormat, BadNumStr, ..])
parse_name_and_year = |str| {
	{ before: name, after: birth_year_str } = str.split_first(" was born in ") ? |_| BadFormat
	birth_year = U16.from_str(birth_year_str)?
	Ok({ name, birth_year })
}

### end snippet question

### start snippet desugared
parse_name_and_year_try : Str -> Try({ name : Str, birth_year : U16 }, [BadFormat, BadNumStr, ..])
parse_name_and_year_try = |str| {
	match str.split_first(" was born in ") {
		Err(_) => Err(BadFormat)
		Ok({ before: name, after: birth_year_str }) => {
			match U16.from_str(birth_year_str) {
				Err(err2) => Err(err2)
				Ok(birth_year) => Ok({ name, birth_year })
			}
		}
	}
}

### end snippet desugared

expect parse_name_and_year("Alice was born in 1990") == Ok({ name: "Alice", birth_year: 1990 })
expect parse_name_and_year_try("Alice was born in 1990") == Ok({ name: "Alice", birth_year: 1990 })

Output

Run this from the directory that has main.roc in it:

$ roc main.roc
Ok({ birth_year: 1990, name: "Alice" })
Ok({ birth_year: 1990, name: "Alice" })