kanaka / mal

mal - Make a Lisp
Other
10k stars 2.53k forks source link

How is `cond` supposed to work? #655

Open metaleap opened 1 month ago

metaleap commented 1 month ago

First off, love this project and having a blast with progressing through it! :tada:

Well, after the first 8 chapters, I now got all the macro, quasiquotation etc tests working except for cond tests: expansion of cond (even just by doing macroexpand in REPL) will have my if special-form (that the macro body is clearly calling) erroring on wrong arg count (2 instead of 3).

And no wonder, looking at cond as defined in process/step8_macros.txt, just indented:

(def cond
    (macro (& xs)
        (if (> (count xs) 0)
            (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))

The if has no "else-branch arg" (the 3rd).

Now what? I must be missing something here?

For reference, here are my macroexpand and eval and quasiquote/unquote.

Like I said, all tests of steps 1 through 8 succeed, except the cond stuff.

metaleap commented 1 month ago

Well, I wrote one from scratch that words for me and expands correctly

(def caseOf
    (macro (cases)
        (if (isEmpty cases)
            :nil
            (let (  (this_case (nth cases 0))
                        (case_cond (nth this_case 0))
                        (case_then (nth this_case 1)))
                `(if ~case_cond
                        ~case_then
                        (caseOf ~(rest cases)))))))

Seems to work well:

࿊  (macroExpand (caseOf [ [:false (do (print :nay) :nope)] [:true (do (print :yay) (+ 1 2))] ]))
(if :false (do (print :nay) :nope) (caseOf ([:true (do (print :yay) (+ 1 2))])))

࿊  (caseOf [ [:false (do (print :nay) :nope)] [:true (do (print :yay) (+ 1 2))] ])
:yay3

Anyone see any issues with that? Also, still eager to learn what I was missing about MAL's own cond! Surely it isn't actually butchered and just looked that way to under-informed me (and my MAL impl).

metaleap commented 1 month ago

Update: only now noticed that MAL's if has the 3rd arg as optional, missed that part before. Still, having updated my MAL impl to use :nil for a missing 3rd if arg, the cond as originally formulated:

(def cond
  (macro (& xs)
    (if (> (count xs) 0)
      (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs)))))))

still results in a stack overflow with 2 args or more :face_with_spiraleyes: my own caseOf formulation works though and as before, all other tests pass `¯_(ツ)/¯`