All Syntax File

Demonstrates all Roc syntax in a single app file. See all module types to view syntax examples for the non-app headers.

Code

import "README.md" as readme : Str # You can also import as List

# Note 1: I tried to demonstrate all Roc syntax (possible in a single app file),
# but I probably forgot some things. Let @Anton know on zulip if you spot something missing, or make a github issue.

## Double hashtag for doc comment
number_operators : I64, I64 -> _
number_operators = |a, b| {
	a_f64 = I64.to_f64(a)
	b_f64 = I64.to_f64(b)

	{
		# binary operators
		sum: a + b,
		diff: a - b,
		prod: a * b,
		div: a_f64 / b_f64,
		div_trunc: a // b,
		rem: a % b,
		eq: a == b,
		neq: a != b,
		lt: a < b,
		lteq: a <= b,
		gt: a > b,
		gteq: a >= b,

		# Not implemented yet:
		# default: None ?? 0,

		# unary operators
		neg: -a,
		# the last item can have a comma too
	}
}

boolean_operators : Bool, Bool -> _
boolean_operators = |a, b| {
	bool_and_keyword: a and b,
	bool_or_keyword: a or b,
	not_a: !a,
}

simple_match : [Red, Green, Blue, BabyBlue] -> Str
simple_match = |color| {
	match color {
		Red => "The color is red."
		Green => "The color is green."
		Blue | BabyBlue => "The color is blue."
	}
}

match_list_patterns : List(U64) -> U64
match_list_patterns = |lst| {
	match lst {
		[] => 0
		[x] => x
		[1, 2, 3] => 6
		[1, 2, ..] => 66
		[2, .., 1] => 88
		[1, .. as tail] => 77 + tail.len()
		[_head, 5] => 55
		[99, x] if x < 4 => 99 + x

		# Note: avoid overusing `_` in a match branch, in general you should
		# try to match all cases explicitly.
		_ => 100
	}
}

match_tag_union_advanced : Try({}, [StdoutErr(Str), Other]) -> Str
match_tag_union_advanced = |try|
# `Try(a, b)` is the tag union `[Ok(a), Err(b)]` under the hood.
	match try {
		Ok(_) =>
			"Success"

		Err(StdoutErr(err)) =>
			"StdoutErr: ${Str.inspect(err)}"

		Err(_) =>
			"Unknown error"
		}

multiline_str : U64 -> Str
multiline_str = |number|
	\\Line 1
	\\Line 2
	\\Line ${number.to_str()}

# end name with `!` for effectful functions
# `=>` shows effectfulness in the type signature
effect_demo! : Str => {}
effect_demo! = |msg|
	echo!("${msg}\n")

question_postfix : List(Str) -> Try(I64, _)
question_postfix = |strings| {
	# `?` to immediately return the error if there is one
	first_str = strings.first()?
	first_num = I64.from_str(first_str)?

	Ok(first_num)
}

# `?` with a right-hand side maps the err payload before early returning.
# The rhs can be a bare tag (applied as a constructor) or any function-like
# expression (e.g. a lambda); the err payload is passed as its argument.
question_with_err_map : List(Str) -> Try(Str, _)
question_with_err_map = |strings| {
	# `? NoFirstError` wraps the err as `NoFirstError(err)` before returning
	first_str = strings.first() ? NoFirstError
	Ok(first_str)
}

question_with_err_lambda : List(Str) -> Try(Str, _)
question_with_err_lambda = |strings| {
	# `? |e| NoFirstError(e)` is the explicit lambda form
	first_str = strings.first() ? |e| NoFirstError(e)
	Ok(first_str)
}

# Use crash for placeholders you want to fill in later.
implement_me_later : Str -> Str
implement_me_later = |str| {
	if str == "" {
		str
	} else {
		crash "not implemented"
	}
}

# for loops can be easier to think about than List.fold (previously `List.walk`)
for_loop = |num_list| {
	var $sum = 0

	for num in num_list {
		$sum = $sum + num
	}

	$sum
}

# break exits a for or while loop early
break_in_for_loop = |bool_list| {
	var $allTrue = True
	for b in bool_list {
		if b == False {
			$allTrue = False
			break
		} else {
			{}
		}
	}
	$allTrue
}

while_loop = |limit| {
	var $count = 0
	var $sum = 0

	while $count < limit {
		$sum = $sum + $count
		$count = $count + 1
	}

	$sum
}

print! = |something| {
	echo!("${Str.inspect(something)}\n")
}

dbg_keyword = || {
	foo : Dec
	foo = 42.0

	dbg foo

	# This variation does not work yet:
	# bar = dbg 43

	foo
}

if_demo : U64 -> Str
if_demo = |num| {
	# every if must have an else branch!
	one_line_if = if num == 1 "One" else "NotOne"

	two_line_if =
		if num == 2
			"Two"
		else
			"NotTwo"

	with_curlies =
		if num == 5 {
			"Five"
		} else {
			"NotFive"
		}

	# else if
	if num == 3
		"Three"
	else if num == 4
		"Four"
	else
		one_line_if.concat(two_line_if).concat(with_curlies)
}

tuple_demo =
# tuples can contain multiple types
	("Roc", 1)

# Here we use a type variable `a` to indicate this function works for a list of any type.
type_var : List(a) -> List(a)
type_var = |lst| lst

destructuring = || {
	tup : (Str, Dec)
	tup = ("Roc", 1.0)
	(str, num) = tup

	rec : { x : Dec, y : Dec }
	rec = { x: 1.0, y: tup.1 } # tuple access with `.index`
	{ x, y } = rec

	(str, num, x, y)
}

NominalTypeRecord := { x : U64 }

# `Type.{ fields }` also works as a pattern, destructuring a nominal type's
# backing record directly.
destructure_nominal_type : NominalTypeRecord -> U64
destructure_nominal_type = |NominalTypeRecord.{ x }| x

# TODO not sure if still planned for implementation
# record_update = {
#     rec = { x: 1, y: 2 }
#     rec2 = { rec & y: 3 }
#     rec2
# }

record_update_2 : { name : Str, age : I64 } -> { name : Str, age : I64 }
record_update_2 = |person| {
	{ ..person, age: 31 }
}

# `..rest` in a record pattern binds every field you did not name as a new
# record, so it doubles as a way to remove a field: `rest` is `person` without `email`.
remove_record_field : { name : Str, age : I64, email : Str } -> { name : Str, age : I64 }
remove_record_field = |person| {
	{ email: _, ..rest } = person
	rest
}

number_literals = {
	usage_based: 5, # defaults to Dec
	explicit_u8: 5.U8, # Note that most of the time you will want to specify the type in the type signature instead.
	explicit_i8: 5.I8,
	explicit_u16: 5.U16,
	explicit_i16: 5.I16,
	explicit_u32: 5.U32,
	explicit_i32: 5.I32,
	explicit_u64: 5.U64,
	explicit_i64: 5.I64,
	explicit_u128: 5.U128,
	explicit_i128: 5.I128,
	# Note: F32, F64, and Dec literals use type inference which doesn't work with Str.inspect so they are omitted here.
	hex: 0x5,
	octal: 0o5,
	binary: 0b0101,
}

# Opaque type
# Useful if you want to hide fields e.g. so users of the type can not access some implementation detail you did not want to expose.
Secret :: {
	key : Str,
}.{
	new : Str -> Secret
	new = |k| { key: k }

	unlock : Secret, Str -> Str
	unlock = |secret, password| {
		if password == "open sesame" {
			"The secret key is: ${secret.key}"
		} else {
			"Wrong password!"
		}
	}
}

# Define a nominal type with a custom is_eq method
Animal := [Dog(Str), Cat(Str)].{
	is_eq = |a, b| match (a, b) {
		(Dog(name1), Dog(name2)) => name1 == name2
		(Cat(name1), Cat(name2)) => name1 == name2
		_ => Bool.False
	}
}

early_return = |arg| {
	first =
		if !arg {
			return 99
		} else {
			"continue"
		}

	# Do some other stuff
	Str.count_utf8_bytes(first)
}

my_concat = Str.concat

# Complex pipeline: chaining static dispatch methods with a lambda
format_names : List(Str) -> Str
format_names = |names|
	names
		.map(|name| name.trim())
		|> Str.join_with(", ")
		|> (|joined| {
			if joined.is_empty() "No names provided" else "Names: ${joined}"
		})

# Tags can have multiple payloads
multi_payload_tag : [Foo(I64, Str), Bar] -> Str
multi_payload_tag = |tag| match tag {
	Foo(num, name) => "Foo with ${num.to_str()} and ${name}"
	Bar => "Just Bar"
}

# Mark a tag union as open using `..`.
# This function accepts any tag union containing at least Red and Green.
color_to_str : [Red, Green, ..] -> Str
color_to_str = |color| match color {
	Red => "red"
	Green => "green"
	_ => "other color"
}

# TODO: Closed tag unions with `..[]]` - syntax not implemented yet
# str_to_color : Str -> [Red, Green, Blue, Other, ..[]]

# Type alias for an extensible tag union. You can use a type var (`others`) like so:
Letters(others) : [A, B, ..others]

# Use the type alias in a function signature. Pass `[C]` as `others`.
letter_to_str : Letters([C]) -> Str
letter_to_str = |letter| match letter {
	A => "A"
	B => "B"
	_ => "other letter"
}

# If you want to define a function that works for any type that has a specific method, you can use `where`:
stringify : a -> Str where [a.to_str : a -> Str]
stringify = |value| value.to_str()

main! = |_args| {
	echo!("Hello, world!\n")
	echo!("Hello, world! (using alias)\n")

	echo!("${Str.inspect(number_operators(10, 5))}\n")
	print!(boolean_operators(Bool.True, Bool.False))

	# pizza operator (|>) is gone, we now have static dispatch instead.
	# It allows you to call methods that are defined on the type (like `Animal.is_eq` above).
	print!("One".concat(" Two"))

	# If you want a very similar style for a function that is not defined on the type but is in scope, you can use `|>`:
	print!("Three" |> my_concat(" Four"))

	echo!("${simple_match(Red)}\n")
	print!(match_list_patterns([1, 10]))
	echo!("${match_tag_union_advanced(Ok({}))}\n")

	echo!("${multiline_str(3)}\n")
	echo!("Unicode escape sequence: \u(00A0)\n")

	effect_demo!("This is an effectful function!")

	print!(question_postfix(["1", "not a number", "100"]))
	print!(question_with_err_map([]))
	print!(question_with_err_lambda([]))

	sum = for_loop([1, 2, 3, 4, 5])
	print!(sum)

	expect sum == 15

	all_true = break_in_for_loop([True, True, False, True, True])
	print!(all_true)

	while_sum = while_loop(5)
	print!(while_sum)

	print!(dbg_keyword())

	echo!("${if_demo(2)}\n")

	print!(tuple_demo)

	print!(type_var(["a", "b"]))

	print!(destructuring())

	print!(destructure_nominal_type(NominalTypeRecord.{ x: 42 }))

	# print!(record_update)

	print!({ x: 10, y: 20 }.x)

	print!(record_update_2({ name: "Alice", age: 30 }))

	print!(remove_record_field({ name: "Alice", age: 30, email: "alice@example.com" }))

	print!(number_literals)

	secret = Secret.new("my_secret_key")
	# This print will not expose internal data.
	print!(secret)
	print!(secret.unlock("open sesame"))

	dog : Animal
	dog = Dog("Fido")
	cat : Animal
	cat = Cat("Whiskers")
	print!(dog == cat)

	print!(early_return(Bool.False))

	print!(stringify(12345))

	# Tags with multiple payloads
	print!(multi_payload_tag(Foo(42, "hello")))

	# Open tag unions with `..`
	# This function accepts [Red, Green, ..] so we can pass Blue too
	print!(color_to_str(Blue))

	# Complex pipeline with arrow lambda
	print!(format_names(["  Alice ", "Bob  ", " Charlie"]))

	# Type alias for extensible tag union
	print!(letter_to_str(A))
	print!(letter_to_str(C)) # C is not in [A, B] but we passed it in the signature of letter_to_str

	print!(readme.contains("Roc"))

	# Commented out so CI tests can pass
	# crash "Avoid using crash in production software!"

	Ok({})
}

# Top level expects only run when using `roc test file.roc`
expect Bool.True != Bool.False

## Multi-line expect that confirms basic math works.
expect {
	x = 4
	y = 5
	x + y == 9
}

Output

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

$ roc main.roc
Hello, world!
Hello, world! (using alias)
{ diff: 5, div: 2, div_trunc: 2, eq: False, gt: True, gteq: True, lt: False, lteq: False, neg: -10, neq: True, prod: 50, rem: 0, sum: 15 }
{ bool_and_keyword: False, bool_or_keyword: True, not_a: False }
"One Two"
"Three Four"
The color is red.
78
Success
Line 1
Line 2
Line 3
Unicode escape sequence:  
This is an effectful function!
Ok(1)
Err(NoFirstError(ListWasEmpty))
Err(NoFirstError(ListWasEmpty))
15.0
False
10.0
[dbg] 42.0
42.0
NotOneTwoNotFive
("Roc", 1.0)
["a", "b"]
("Roc", 1.0, 1.0, 1.0)
42
10.0
{ age: 31, name: "Alice" }
{ age: 30, name: "Alice" }
{ binary: 5.0, explicit_i128: 5, explicit_i16: 5, explicit_i32: 5, explicit_i64: 5, explicit_i8: 5, explicit_u128: 5, explicit_u16: 5, explicit_u32:5, explicit_u64: 5, explicit_u8: 5, hex: 5.0, octal: 5.0, usage_based: 5.0 }
<opaque>
"The secret key is: my_secret_key"
False
99
"12345.0"
"Foo with 42 and hello"
"other color"
"Names: Alice, Bob, Charlie"
"A"
"other letter"
True