;; lib/flow/tests/railway.sx — Phase 6: railway-oriented composition (attempt). (define flow-rail-pass 0) (define flow-rail-fail 0) (define flow-rail-fails (list)) (define flow-rail-test (fn (name actual expected) (if (= actual expected) (set! flow-rail-pass (+ flow-rail-pass 1)) (begin (set! flow-rail-fail (+ flow-rail-fail 1)) (append! flow-rail-fails {:name name :expected expected :actual actual}))))) (define flow-r (fn (src) (flow-run src))) ;; ── attempt — short-circuit on the first (fail ...) ───────────── (flow-rail-test "attempt: threads like sequence when nothing fails" (flow-r "(flow/start (attempt (lambda (x) (+ x 1)) (lambda (x) (* x 10))) 4)") 50) (flow-rail-test "attempt: empty is identity" (flow-r "(flow/start (attempt) 7)") 7) (flow-rail-test "attempt: returns the first failure" (flow-r "(failed? (flow/start (attempt (lambda (x) (fail (quote bad))) (lambda (x) (* x 10))) 4))") true) (flow-rail-test "attempt: the failure carries its reason" (flow-r "(fail-reason (flow/start (attempt (lambda (x) x) (lambda (x) (fail (quote rejected)))) 4))") "rejected") (flow-rail-test "attempt: nodes after a failure do not run" (flow-r "(define ran 0) (flow/start (attempt (lambda (x) (fail (quote stop))) (lambda (x) (begin (set! ran (+ ran 1)) x))) 0) ran") 0) (flow-rail-test "attempt: a failed input short-circuits immediately" (flow-r "(define ran 0) (fail-reason (flow/start (attempt (lambda (x) (begin (set! ran (+ ran 1)) x))) (fail (quote pre))))") "pre") (flow-rail-test "attempt: middle failure halts the chain" (flow-r "(define ran 0) (flow/start (attempt (lambda (x) (+ x 1)) (lambda (x) (fail (quote mid))) (lambda (x) (begin (set! ran (+ ran 1)) x))) 5) ran") 0) ;; ── attempt + recover (rejoin the happy track) ────────────────── (flow-rail-test "attempt + recover: recover turns a failure into a value" (flow-r "(flow/start (recover (attempt (lambda (x) (if (> x 0) x (fail (quote non-positive)))) (lambda (x) (* x 2))) (flow-const 0)) -5)") 0) (flow-rail-test "attempt + recover: happy path passes recover through" (flow-r "(flow/start (recover (attempt (lambda (x) (if (> x 0) x (fail (quote non-positive)))) (lambda (x) (* x 2))) (flow-const 0)) 5)") 10) (flow-rail-test "attempt: validation pipeline reports the failing stage" (flow-r "(defflow validate (attempt (lambda (s) (if (>= (string-length s) 3) s (fail (quote too-short)))) (lambda (s) (if (<= (string-length s) 8) s (fail (quote too-long)))) (lambda (s) (list (quote ok) (string-length s))))) (list (fail-reason (flow/start validate \"hi\")) (flow/start validate \"hello\"))") (list "too-short" (list "ok" 5))) (define flow-rail-tests-run! (fn () {:total (+ flow-rail-pass flow-rail-fail) :passed flow-rail-pass :failed flow-rail-fail :fails flow-rail-fails}))