bobbicodes / bobbi-lisp

Interactive Lisp environment for learning Clojure
https://bobbicodes.github.io/bobbi-lisp/
0 stars 0 forks source link

Weird bug in `assoc` #29

Closed bobbicodes closed 11 months ago

bobbicodes commented 11 months ago

I was trying to figure out how to modify assoc to repect equality of aggregate values.

The problem statement is simple: javascript's .set() on maps doesn't recognize keys that aren't simple values, for example, a vector:

{[1] 2}

So if we call (assoc {[1] 2} [1] 3), what we want is to get a new map of {[1] 3}, not a map with a new key with duplicate keys, i.e. {[1] 2 [1] 3}.

But the actual result is even weirder...

(assoc {[1] 2} [1] 3)
=> 
{[1] 2
 [1] 2}

So naturally, I started printing things to understand the different parts of execution. And to my surprise... the return value of assoc as printed in the console is actually the expectedly wrong value, {[1] 2 [1] 3}.

I have no idea how to explain this or correct it. And it's not like a "sweep it under the rug, maybe fix one day but maybe never" kind of thing. It's critical to the operation of a language.

bobbicodes commented 11 months ago

Apparently this behavior was being caused by the pretty printer! I disabled it and now we have the "correct" wrong result:

(assoc {[1] 2} [1] 3)
=> 
{[1] 2 [1] 3} 
bobbicodes commented 11 months ago

So it seems we have 2 issues here. I solved the first one with the following code:

var assoc_keys = new Set()
    for (let i = 1; i < arguments.length; i += 2) {
        assoc_keys.add(arguments[i])
    }
    var new_map = new Map()
    for (const [key, value] of hm) {
        if (!contains_Q(assoc_keys, key)) {
            new_map.set(key, value)
        }
    }
    for (var i = 1; i < arguments.length; i += 2) {
        var ktoken = arguments[i],
            vtoken = arguments[i + 1];
        new_map.set(ktoken, vtoken)
    }
    return new_map;

It builds a new map with each element except ones which match one being passed to assoc, then sets the assoc args. Cool!

(assoc {[1] 2} [1] 3)
=> 
{[1] 3} 

So then we have the matter of the pretty printer. I just recently added that so I don't understand it much. But I can still reproduce it:

{[1] 2 [1] 3}
=> 
{[1] 2
 [1] 2} 

It might be that the map is semantically invalid, so it's just printing the value of [1] as it should. I think I'll just close this and call it expected behavior when printing maps with duplicate keys.