#lang racket

(provide left right left? right? squash
         >>= return reduce <*> e-map do)

(struct either (tag value))

(define (left val)
  (either 'left val))

(define (right val)
  (either 'right val))

(define (left? val)
  (and (either? val) (eq? (either-tag val) 'left)))

(define (right? val)
  (not (left? val)))

(define (>>= e f)
  (if (eq? (either-tag e) 'left)
    e
    (f (either-value e))))

(define (return x)
  (right x))

(define (reduce f-left f-right e)
  (if (eq? (either-tag e) 'left)
    (f-left (either-value e))
    (f-right (either-value e))))

(define (<*> f e)
  (cond
    ((eq? 'left (either-tag f))
     f)
    ((eq? 'left (either-tag e))
     e)
    (else
      (right ((either-value f) (either-value e))))))

(define (e-map f e)
  (if (eq? 'left (either-tag e))
    e
    (right (f (either-value e)))))

(define (squash e)
  (if (and (right? e) (either? (either-value e)))
    (either-value e)
    e))

; (do
;  (<- var1 exp1)
;  (exp2)
;  (let var2 exp3)
;  (return var2))
;
;  -->
; (>>= exp1 (lambda (var1) (exp2) (let ((var2 exp3)) (return var2))))

(define-syntax (do stx)
  (define (do->lambda exprs)
    (define expr (car exprs))
    (cond
      ((and (pair? expr) (eq? (car expr) '<-))
       `(>>= ,(caddr expr) (lambda (,(cadr expr)) ,(do->lambda (cdr exprs)))))
      ((and (pair? expr) (eq? (car expr) 'let))
       `(let ((,(cadr expr) ,(caddr expr))) ,(do->lambda (cdr exprs))))
      ((null? (cdr exprs))
       expr)
      (else
        `(begin ,expr ,(do->lambda (cdr exprs))))))
  (let* ((ast (syntax->datum stx))
         (transformed (do->lambda (cdr ast))))
    (datum->syntax stx transformed)))
