Skip to main content

Overview

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.

Type Definition

type Result e a = Ok a | Err e
Type Parameters:
  • e - The error type
  • a - The success value type
Constructors:
  • Ok a - Represents a successful computation with a result value
  • Err e - Represents a failed computation with an error value

Importing

import Result

Why Result?

Traditional exception-based error handling has several issues:
// JavaScript - unchecked exceptions
function parseJSON(text) {
    return JSON.parse(text);  // throws exception on error
}

let data = parseJSON(input);  // might crash!
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.

Creating Result Values

import Result
import Elara.Prim

-- Explicit construction
let success : Result String Int = Ok 42
let failure : Result String Int = Err "Something went wrong"

-- From computation
def parseInt : String -> Result String Int
let 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 42
let result2 = parseInt "abc"  -- Err "Cannot parse: abc"

Functions

Status Checks

isOk

isOk
Result e a -> Bool
Returns True if the result is Ok, False if it’s Err.
Signature:
def isOk : Result e a -> Bool
Example:
import Result

let success = Ok 42
let failure = Err "error"

let check1 = isOk success  -- True
let check2 = isOk failure  -- False
Implementation:
let isOk res =
    match res with
        Ok _ -> True
        Err _ -> False

isErr

isErr
Result e a -> Bool
Returns True if the result is Err, False if it’s Ok.
Signature:
def isErr : Result e a -> Bool
Example:
import Result

let success = Ok 42
let failure = Err "error"

let check1 = isErr success  -- False
let check2 = isErr failure  -- True
Implementation:
let isErr res =
    match res with
        Ok _ -> False
        Err _ -> True

Transformations

map

map
(a -> b) -> Result e a -> Result e b
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 Result
import Elara.Prim

let result = Ok 5
let doubled = map (\x -> x * 2) result  -- Ok 10

let 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
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

mapErr

mapErr
(e -> f) -> Result e a -> Result f a
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:
import Result
import String

let result = Err 404
let withMsg = mapErr (\code -> "Error " ++ toString code) result
-- Err "Error 404"

let success = Ok "data"
let unchanged = mapErr (\code -> "Error " ++ toString code) success
-- Ok "data"
Implementation:
let mapErr f res =
    match res with
        Ok a -> Ok a
        Err e -> Err (f e)
let result = Ok 5
let doubled = map (\x -> x * 2) result
-- Ok 10

Chaining Operations

bind

bind
Result e a -> (a -> Result e b) -> Result e b
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 Result
import Elara.Prim

def parseInt : String -> Result String Int
let parseInt str = -- implementation

def safeDiv : Int -> Int -> Result String Int
let safeDiv x y =
    if y == 0 then
        Err "Division by zero"
    else
        Ok (x / y)

-- Chain operations
let compute : String -> String -> Result String Int
let compute numStr divStr =
    bind (parseInt numStr) (\num ->
    bind (parseInt divStr) (\divisor ->
    safeDiv num divisor))

let result1 = compute "10" "2"  -- Ok 5
let 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.

Pattern Matching

The most common way to work with Result values is through pattern matching:

Basic Pattern Matching

import Result
import String
import Elara.Prim

def processResult : Result String Int -> String
let processResult res =
    match res with
        Ok value -> "Success: " ++ toString value
        Err msg -> "Error: " ++ msg

let msg1 = processResult (Ok 42)           -- "Success: 42"
let msg2 = processResult (Err "failed")    -- "Error: failed"

Nested Results

import Result

def parseAndValidate : String -> Result String Int
let 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"

Default Values

import Result

def getOrDefault : a -> Result e a -> a
let getOrDefault default res =
    match res with
        Ok x -> x
        Err _ -> default

let value = getOrDefault 0 (Ok 42)    -- 42
let value2 = getOrDefault 0 (Err "")  -- 0

Common Patterns

Safe Division

import Result
import Elara.Prim

def safeDiv : Int -> Int -> Result String Int
let safeDiv x y =
    if y == 0 then
        Err "Division by zero"
    else
        Ok (x / y)

-- Usage
let result = safeDiv 10 2
match result with
    Ok quotient -> println ("Result: " ++ toString quotient)
    Err msg -> println ("Error: " ++ msg)

File Reading

import Result
import Elara.Prim

def safeReadFile : String -> IO (Result String String)
let safeReadFile path =
    -- In practice, you'd wrap the IO operation
    readFile path >>= \content ->
    -- Return success (error handling omitted for simplicity)
    pure (Ok content)

Validation

import Result
import String

type ValidationError = TooShort | TooLong | InvalidChars

def validateUsername : String -> Result ValidationError String
let validateUsername name =
    let len = String.length name
    if len < 3 then
        Err TooShort
    else if len > 20 then
        Err TooLong
    else
        Ok name

Combining Multiple Results

Sequential Operations

import Result

-- Using bind to chain operations
def processUser : String -> String -> Result String User
let processUser nameStr ageStr =
    bind (parseInt ageStr) (\age ->
    bind (validateName nameStr) (\name ->
    Ok (User name age)))

Collecting Results

import Result
import List

-- Process a list of results
def 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 results

let 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"

Complete Example

import Prelude
import Result
import String
import Elara.Prim

-- Parse and validate integer input
def parsePositiveInt : String -> Result String Int
let 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 validation
def safeDivWithValidation : String -> String -> Result String Int
let 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 display
def formatResult : Result String Int -> String
let formatResult res =
    match res with
        Ok value -> "Success: " ++ toString value
        Err msg -> "Error: " ++ msg

let 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

Advanced Patterns

Result Combinators

import Result

-- Apply a function wrapped in a Result to a value wrapped in a Result
def apply : Result e (a -> b) -> Result e a -> Result e b
let 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 function
def map2 : (a -> b -> c) -> Result e a -> Result e b -> Result e c
let map2 f resA resB =
    bind resA (\a ->
    bind resB (\b ->
    Ok (f a b)))

Converting Between Types

import Result
import Option

-- Convert Option to Result
def optionToResult : e -> Option a -> Result e a
let 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 a
let resultToOption res =
    match res with
        Ok x -> Some x
        Err _ -> None

Best Practices

Use Result when:
  • Operations can fail and you need to provide error information
  • You want explicit, compile-time checked error handling
  • Errors should be handled by the caller
  • You need to chain multiple fallible operations
Error Types:
  • Use descriptive error types (ADTs) instead of just strings when possible
  • Keep error types simple and actionable
  • Consider using a custom error type hierarchy for complex applications

Comparison with Option

-- Use Result when you need error information
def parseJSON : String -> Result String JsonValue
let parseJSON input =
    -- Returns Err with parse error message
    Err "Unexpected token at position 42"

Error Handling Strategies

Return errors immediately without recovery:
def process : Input -> Result Error Output
let process input =
    bind (validate input) (\validated ->
    bind (transform validated) (\transformed ->
    Ok transformed))

See Also

Option Type

Optional values without error info

Pattern Matching

Learn pattern matching syntax

IO and Effects

Error handling strategies

Examples

See Result in real examples

Build docs developers (and LLMs) love