Pattern Matching

The when keyword replaces the transitional switch statement found in many other languages. It provides a more powerful and expressive way to handle conditional logic and pattern matching in Conjure.

Basic Usage

When using when as a statement, it executes different blocks of code based on the value of an expression. Each case is defined using the case keyword followed by the value to match and a colon.

enum Color { Red, Green, Blue }

var myColor = Color.Red

when myColor {
	case .Red: io.println("myColor is red")
	case .Green: io.println("myColor is green")
	case .Blue: io.println("myColor is blue")
}

When matching enum values, you can use the shorthand .Value syntax since the type is already known from the expression being matched.

Else Case

Use else to handle any values not explicitly matched. For non-exhaustive types (like integers or strings), an else case is required.

when dayOfWeek {
	case 0: io.println("Sunday")
	case 6: io.println("Saturday")
	else: io.println("Weekday")
}

Multi-Statement Cases

Cases can contain multiple statements. Use a block or place statements on subsequent lines.

when color {
	case .Red:
		io.println("Color is red")
		io.println("This is a warm color")
	case .Blue:
		io.println("Color is blue")
		io.println("This is a cool color")
	else: io.println("Some other color")
}

Fallthrough

By default, each case in a when statement is independent. After executing a case’s body, control transfers out of the when block. To explicitly continue execution into the next case, use the fallthrough keyword.

enum Priority { Low, Medium, High, Critical }

func handlePriority(p Priority) {
	when p {
		case .Critical:
			io.println("ALERT: Critical priority!")
			fallthrough
		case .High:
			io.println("Notifying on-call team")
			fallthrough
		case .Medium:
			io.println("Logging to monitoring system")
		case .Low:
			io.println("Recorded in debug log")
	}
}

In this example, .Critical will execute all three messages, .High will execute two, and .Medium or .Low will execute only their own message.

Note: fallthrough can only be used in when statements that match on enum or sum types. It cannot be used in boolean-style when statements (those without a tag expression).

Exhaustiveness Checking

When working with enum and sum types, when statements must be exhaustively checked; meaning all possible variants must be handled. This provides compile-time safety.

enum Direction { North, South, East, West }

var dir = Direction.North

// This will compile - all cases handled
when dir {
	case .North: io.println("Going north")
	case .South: io.println("Going south")
	case .East: io.println("Going east")
	case .West: io.println("Going west")
}

// This would also compile - else covers remaining cases
when dir {
	case .North: io.println("Going north")
	else: io.println("Going some other direction")
}

Pattern Matching on Sum Types

Sum types (tagged unions) can be matched to determine which variant is currently held. Inside the case block, the value is automatically narrowed to that type.

const IntOrStr = i32 | string

var value IntOrStr = 42

when value {
	case i32:
		io.println("Value is an integer: #{value}")
	case string:
		io.println("Value is a string with length #{value.len}")
}

When Expressions Proposed

when can also be used as an expression to produce values based on conditions.

var colorName = when color {
	case .Red: "Red"
	case .Green: "Green"
	case .Blue: "Blue"
}