Arc Forumnew | comments | leaders | submitlogin
Create your own collection: bidirectional tables
4 points by almkglor 5906 days ago | 4 comments
On line 24 of app.arc, we have:

  ; idea: a bidirectional table, so don't need two vars (and sets)

  (= cookie->user* (table) user->cookie* (table) logins* (table))
Well, with the magic of settable-fn or settable-fn2, pg's idea has been made real. Get it from arc-wiki.git, load "lib/bidir-table", and:

  arc> (= test (bidir-table))
  #4(....)
  arc> (= test!x 'y)
  y
  arc> test!x
  y
  arc> test!y
  x
  arc> (= test!y 42)
  42
  arc> test!42
  y
  arc> test!x
  nil
Caveat: (keys ...) on a bidir-table will only return those objects that were assigned to in key position. This is in order to prevent 'maptable from going through pairs twice.


1 point by CatDancer 5906 days ago | link

Your approach has the elegance of needing only one access method, however I'd prefer to see setting one side of the bitable not overwrite a value stored on the other side.

If I should happen to choose a username that matches your cookie value and I login, I will destroy your session.

-----

1 point by almkglor 5906 days ago | link

Make them different types, I suppose, say capture cookie values into cons cells, or adding a character to cookies which is disallowed in usernames. Admittedly hackish, but then otherwise if you get my cookie as your username, looking up my cookie gets your cookie, which still destroy my session.

-----

1 point by CatDancer 5906 days ago | link

Sorry, I wasn't clear: I meant I wanted the two sides of the bitable to be separate: setting side a would automatically set side b and vis. versa., but I would still be choosing to do a look up in side a or side b, and I'd still be choosing to set side a or side b (though with the bidirectional table setting side a X Y would be the same as setting side b Y X).

-----

1 point by almkglor 5906 days ago | link

Can do, you could just build something like this:

  (def bidir-table-sided ()
    (with (a-to-b (table)
           b-to-a (table)
           erase-link
           (fn (k-to-v v-to-k k)
             (let v (k-to-v k)
               (= (v-to-k v) nil
                  (k-to-v k) nil)))
           report-error (fn (side) (err:string "bidir-table-sided: not side a or b - " side)))
      (add-attachment
        '= (fn (v side k)
             (let (k-to-v v-to-k)
               (case side
                 a (list a-to-b b-to-a)
                 b (list b-to-a a-to-b)
                   (report-error side))
               (if
                 (no k)
                   (err "bidir-table-sided: can't assign nil key")
                 v
                   (do
                      ;first erase
                      (erase-link k-to-v v-to-k k)
                      (erase-link v-to-k k-to-v v)
                      ;now link them
                      (= (v-to-k v) k)
                      (= (k-to-v k) v))
                 ; else delete
                   (erase-link k-to-v v-to-k k))))
        ; no keys attachment: not a real table, since
        ; we specify a side
        (fn (side k)
          (case side
            a (a-to-b k)
            b (b-to-a k)
              (report-error side))))))
To use:

  (= (foo 'a 42) 1)
  (foo 'b 1)
Can obviously be extended so that the user can specify the sides, for example you can just specify 'user and 'cookie as the side, this is just proof-of-concept.

-----