The Result type provides a type-safe way to handle operations that can fail with errors. Unlike exceptions, Result values make error handling explicit in the type system, ensuring that errors cannot be silently ignored.
Traditional exception-based error handling has several issues:
// JavaScript - unchecked exceptionsfunction parseJSON(text) { return JSON.parse(text); // throws exception on error}let data = parseJSON(input); // might crash!
Result makes errors explicit and checked at compile time:
-- Elara - explicit error handlingdef parseJSON : String -> Result String JsonValuelet result = parseJSON inputmatch result with Ok data -> processData data Err msg -> println ("Parse error: " ++ msg)
Result is similar to Either in Haskell, Result in Rust, or Try in Scala. It’s a fundamental pattern for explicit error handling in functional programming.
import Resultimport Elara.Prim-- Explicit constructionlet success : Result String Int = Ok 42let failure : Result String Int = Err "Something went wrong"-- From computationdef parseInt : String -> Result String Intlet parseInt str = -- Simplified example (actual parsing would be more complex) if str == "42" then Ok 42 else Err ("Cannot parse: " ++ str)let result1 = parseInt "42" -- Ok 42let result2 = parseInt "abc" -- Err "Cannot parse: abc"
Transforms the success value of a Result, leaving errors unchanged.
Signature:
def map : (a -> b) -> Result e a -> Result e b
Description:Applies a function to the value inside an Ok, transforming it to a new value. If the Result is an Err, it’s passed through unchanged.Example:
import Resultimport Elara.Primlet result = Ok 5let doubled = map (\x -> x * 2) result -- Ok 10let error = Err "failed"let unchanged = map (\x -> x * 2) error -- Err "failed"
Implementation:
let map f res = match res with Ok a -> Ok (f a) Err e -> Err e
Why is map useful?
map lets you transform successful results without having to manually pattern match every time. It’s especially useful when chaining operations:
let result = parseNumber input |> map (\x -> x * 2) |> map (\x -> x + 1) |> map toString
Transforms the error value of a Result, leaving success values unchanged.
Signature:
def mapErr : (e -> f) -> Result e a -> Result f a
Description:Applies a function to the error inside an Err, transforming it to a new error type. If the Result is Ok, it’s passed through unchanged.Example:
Chains operations that return Results, also known as flatMap or >>= in other languages.
Signature:
def bind : Result e a -> (a -> Result e b) -> Result e b
Description:Sequences two operations that can fail. If the first Result is Ok, applies the function to its value. If it’s Err, short-circuits and returns the error.Example:
import Resultimport Elara.Primdef parseInt : String -> Result String Intlet parseInt str = -- implementationdef safeDiv : Int -> Int -> Result String Intlet safeDiv x y = if y == 0 then Err "Division by zero" else Ok (x / y)-- Chain operationslet compute : String -> String -> Result String Intlet compute numStr divStr = bind (parseInt numStr) (\num -> bind (parseInt divStr) (\divisor -> safeDiv num divisor))let result1 = compute "10" "2" -- Ok 5let result2 = compute "10" "0" -- Err "Division by zero"let result3 = compute "abc" "2" -- Err "Parse error: abc"
Implementation:
let bind res f = match res with Ok a -> f a Err e -> Err e
bind is crucial for chaining operations that can fail. It automatically propagates errors, so you don’t need to check each intermediate result manually.
import Resultdef parseAndValidate : String -> Result String Intlet parseAndValidate str = let parsed = parseInt str match parsed with Err e -> Err e Ok num -> if num > 0 then Ok num else Err "Number must be positive"
import Resultdef getOrDefault : a -> Result e a -> alet getOrDefault default res = match res with Ok x -> x Err _ -> defaultlet value = getOrDefault 0 (Ok 42) -- 42let value2 = getOrDefault 0 (Err "") -- 0
import Resultimport Elara.Primdef safeDiv : Int -> Int -> Result String Intlet safeDiv x y = if y == 0 then Err "Division by zero" else Ok (x / y)-- Usagelet result = safeDiv 10 2match result with Ok quotient -> println ("Result: " ++ toString quotient) Err msg -> println ("Error: " ++ msg)
import Resultimport Stringtype ValidationError = TooShort | TooLong | InvalidCharsdef validateUsername : String -> Result ValidationError Stringlet validateUsername name = let len = String.length name if len < 3 then Err TooShort else if len > 20 then Err TooLong else Ok name
import Resultimport List-- Process a list of resultsdef sequence : List (Result e a) -> Result e (List a)let sequence results = let go acc remaining = match remaining with Nil -> Ok (List.reverse acc) Cons (Ok x) xs -> go (Cons x acc) xs Cons (Err e) _ -> Err e go Nil resultslet results = [Ok 1, Ok 2, Ok 3]let combined = sequence results -- Ok [1, 2, 3]let withError = [Ok 1, Err "fail", Ok 3]let failed = sequence withError -- Err "fail"
import Preludeimport Resultimport Stringimport Elara.Prim-- Parse and validate integer inputdef parsePositiveInt : String -> Result String Intlet parsePositiveInt str = let parsed = parseInt str -- Assume this exists bind parsed (\num -> if num > 0 then Ok num else Err "Number must be positive")-- Safe division with validationdef safeDivWithValidation : String -> String -> Result String Intlet safeDivWithValidation numStr divStr = bind (parsePositiveInt numStr) (\num -> bind (parsePositiveInt divStr) (\div -> if div == 0 then Err "Cannot divide by zero" else Ok (num / div)))-- Format result for displaydef formatResult : Result String Int -> Stringlet formatResult res = match res with Ok value -> "Success: " ++ toString value Err msg -> "Error: " ++ msglet main = let r1 = safeDivWithValidation "10" "2" let r2 = safeDivWithValidation "10" "0" let r3 = safeDivWithValidation "-5" "2" println (formatResult r1) *> println (formatResult r2) *> println (formatResult r3)-- Output:-- Success: 5-- Error: Cannot divide by zero-- Error: Number must be positive
import Result-- Apply a function wrapped in a Result to a value wrapped in a Resultdef apply : Result e (a -> b) -> Result e a -> Result e blet apply resF resA = match (resF, resA) with (Ok f, Ok a) -> Ok (f a) (Err e, _) -> Err e (_, Err e) -> Err e-- Combine two Results with a functiondef map2 : (a -> b -> c) -> Result e a -> Result e b -> Result e clet map2 f resA resB = bind resA (\a -> bind resB (\b -> Ok (f a b)))
import Resultimport Option-- Convert Option to Resultdef optionToResult : e -> Option a -> Result e alet optionToResult err opt = match opt with Some x -> Ok x None -> Err err-- Convert Result to Option (discarding error)def resultToOption : Result e a -> Option alet resultToOption res = match res with Ok x -> Some x Err _ -> None
-- Use Result when you need error informationdef parseJSON : String -> Result String JsonValuelet parseJSON input = -- Returns Err with parse error message Err "Unexpected token at position 42"
def process : Input -> Result Error Outputlet process input = bind (validate input) (\validated -> bind (transform validated) (\transformed -> Ok transformed))
Provide fallback values for errors:
def processWithDefault : Input -> Outputlet processWithDefault input = match process input with Ok output -> output Err _ -> defaultOutput
Collect multiple errors before failing:
def validateAll : List Input -> Result (List Error) (List Output)let validateAll inputs = -- Collect all validation errors -- Return all errors, not just the first