# Result/Option Helpers for Seq
#
# This module provides helper words for working with Result and Option patterns.
# Since Seq doesn't support generic types, you define your own Result/Option unions
# with the specific types you need, then use these helpers.
#
# ## Defining Your Own Result Type
#
# # For operations that return Int or fail with String error:
# union IntResult {
# Ok { value: Int }
# Err { error: String }
# }
#
# # For operations that return String or fail:
# union StringResult {
# Ok { value: String }
# Err { error: String }
# }
#
# ## Usage Pattern
#
# include std:result
#
# union IntResult { Ok { value: Int } Err { error: String } }
#
# : safe-divide ( Int Int -- IntResult )
# dup 0 = if
# drop "Division by zero" Make-Err
# else
# divide Make-Ok
# then
# ;
#
# : main ( -- Int )
# 10 2 safe-divide
# result-ok? if
# result-unwrap int->string io.write-line
# else
# "Error: " swap result-unwrap-err string.concat io.write-line
# then
# 0
# ;
#
# ## Result Convention
#
# Result-like unions should follow this convention:
# - First variant (tag 0) = success case with value
# - Second variant (tag 1) = error case with error info
#
# ## Option Convention
#
# Option-like unions should follow this convention:
# - First variant (tag 0) = Some/present with value
# - Second variant (tag 1) = None/absent (no fields)
#
# ## Helper Words
#
# Predicates (keep value on stack):
# - result-ok?: ( Result -- Result Int ) - Check if Ok (tag 0)
# - result-err?: ( Result -- Result Int ) - Check if Err (tag 1)
# - option-some?: ( Option -- Option Int ) - Check if Some (tag 0)
# - option-none?: ( Option -- Option Int ) - Check if None (tag 1)
#
# Unwrapping:
# - result-unwrap: ( Result -- value ) - Extract Ok value (panics on Err)
# - result-unwrap-err: ( Result -- error ) - Extract Err value (panics on Ok)
# - result-unwrap-or: ( Result default -- value ) - Extract Ok or use default
# - option-unwrap: ( Option -- value ) - Extract Some value (panics on None)
# - option-unwrap-or: ( Option default -- value ) - Extract Some or use default
#
# Combinators:
# - result-map: ( Result Quotation -- Result ) - Transform Ok value
# - result-bind: ( Result Quotation -- Result ) - Chain fallible operations
# - option-map: ( Option Quotation -- Option ) - Transform Some value
# - option-bind: ( Option Quotation -- Option ) - Chain optional operations
#
# ## Point-Free Chaining Example
#
# # Chain fallible operations:
# input [ parse ] result-bind [ validate ] result-bind [ process ] result-bind
#
# =============================================================================
# RESULT HELPERS
# =============================================================================
# Check if result is Ok
# Stack: ( Result -- Result Bool )
: result-ok? ( Variant -- Variant Bool )
dup variant.tag :Ok symbol.=
;
# Check if result is Err
# Stack: ( Result -- Result Bool )
: result-err? ( Variant -- Variant Bool )
dup variant.tag :Err symbol.=
;
# Extract Ok value - panics if Err
# Stack: ( Result -- value )
: result-unwrap ( Variant -- Variant )
dup variant.tag :Ok symbol.= if
0 variant.field-at
else
"PANIC: result-unwrap called on Err" io.write-line
result-panic
then
;
# Extract Err value - panics if Ok
# Stack: ( Result -- error )
: result-unwrap-err ( Variant -- Variant )
dup variant.tag :Err symbol.= if
0 variant.field-at
else
"PANIC: result-unwrap-err called on Ok" io.write-line
result-panic
then
;
# Extract Ok value or return default
# Stack: ( Result default -- value )
# Note: default should be same type as Ok's value field
: result-unwrap-or ( Result Default -- Default )
swap # ( Default Result )
dup variant.tag :Ok symbol.= if # is Ok?
0 variant.field-at nip # ( value ) - drop default, return Ok value
else
drop # ( Default ) - drop Result, return default
then
;
# Panic helper - infinite loop
: result-panic ( -- )
result-panic
;
# =============================================================================
# RESULT COMBINATORS
# =============================================================================
# Transform the Ok value, pass through Err unchanged
# The quotation should transform: ( value -- new-value )
# Result is reconstructed using variant.make-1 with :Ok tag
# Stack: ( Result Quotation -- Result )
: result-map ( Variant [ -- ] -- Variant )
swap # ( Quotation Result )
dup variant.tag :Ok symbol.= if # ( Quotation Result ) - is Ok?
0 variant.field-at # ( Quotation value )
swap call # ( new-value )
:Ok variant.make-1 # ( Result ) - wrap as Ok
else
nip # ( Result ) - keep original Err
then
;
# Transform the Ok value with custom constructor
# Usage: my-result [ double ] [ Make-Ok ] result-map-with
# Stack: ( Result Quotation OkConstructor -- Result )
: result-map-with ( Variant [ -- ] [ -- ] -- Variant )
rot # ( Quotation OkCtor Result )
dup variant.tag :Ok symbol.= if # is Ok?
0 variant.field-at # ( Quotation OkCtor value )
rot call # ( OkCtor new-value )
swap call # ( Result )
else
rot drop rot drop # ( Result ) - keep original Err
then
;
# Chain fallible operations
# The quotation should return a Result: ( value -- Result )
# Stack: ( Result Quotation -- Result )
: result-bind ( Variant [ -- ] -- Variant )
swap # ( Quotation Result )
dup variant.tag :Ok symbol.= if # ( Quotation Result ) - is Ok?
0 variant.field-at # ( Quotation value )
swap call # ( Result )
else
nip # ( Result ) - keep original Err, drop quotation
then
;
# =============================================================================
# OPTION HELPERS
# =============================================================================
# Check if option is Some
# Stack: ( Option -- Option Bool )
: option-some? ( Variant -- Variant Bool )
dup variant.tag :Some symbol.=
;
# Check if option is None
# Stack: ( Option -- Option Bool )
: option-none? ( Variant -- Variant Bool )
dup variant.tag :None symbol.=
;
# Extract Some value - panics if None
# Stack: ( Option -- value )
: option-unwrap ( Variant -- Variant )
dup variant.tag :Some symbol.= if
0 variant.field-at
else
"PANIC: option-unwrap called on None" io.write-line
result-panic
then
;
# Extract Some value or return default
# Stack: ( Option default -- value )
# Note: default should be same type as Some's value field
: option-unwrap-or ( Option Default -- Default )
swap # ( Default Option )
dup variant.tag :Some symbol.= if # is Some?
0 variant.field-at nip # ( value ) - drop default, return Some value
else
drop # ( Default ) - drop Option, return default
then
;
# =============================================================================
# OPTION COMBINATORS
# =============================================================================
# Transform the Some value, pass through None unchanged
# The quotation should transform: ( value -- new-value )
# Result is reconstructed using variant.make-1 with :Some tag
# Stack: ( Option Quotation -- Option )
: option-map ( Variant [ -- ] -- Variant )
swap # ( Quotation Option )
dup variant.tag :Some symbol.= if # ( Quotation Option ) - is Some?
0 variant.field-at # ( Quotation value )
swap call # ( new-value )
:Some variant.make-1 # ( Option ) - wrap as Some
else
nip # ( Option ) - keep original None
then
;
# Transform the Some value with custom constructor
# Usage: my-option [ double ] [ Make-Some ] option-map-with
# Stack: ( Option Quotation SomeConstructor -- Option )
: option-map-with ( Variant [ -- ] [ -- ] -- Variant )
rot # ( Quotation SomeCtor Option )
dup variant.tag :Some symbol.= if # is Some?
0 variant.field-at # ( Quotation SomeCtor value )
rot call # ( SomeCtor new-value )
swap call # ( Option )
else
rot drop rot drop # ( Option ) - keep original None
then
;
# Chain optional operations
# The quotation should return an Option: ( value -- Option )
# Stack: ( Option Quotation -- Option )
: option-bind ( Variant [ -- ] -- Variant )
swap # ( Quotation Option )
dup variant.tag :Some symbol.= if # ( Quotation Option ) - is Some?
0 variant.field-at # ( Quotation value )
swap call # ( Option )
else
nip # ( Option ) - keep original None, drop quotation
then
;