Arc Forumnew | comments | leaders | submitlogin
Contract ssyntax
9 points by fallintothis 2620 days ago | 4 comments
In Arc, symbols that contain so-called ssyntax characters stand as abbreviations for expressions. You can see unabbreviated forms with ssexpand.

  arc> (ssexpand 'f.x)
  (f x)
  arc> (ssexpand 'f!x)
  (f (quote x))
  arc> (ssexpand '.x)
  (get x)
  arc> (ssexpand '!x)
  (get (quote x))
  arc> (ssexpand '~f)
  (complement f)
  arc> (ssexpand 'f:g)
  (compose f g)
  arc> (ssexpand 'f&g)
  (andf f g)
Ssyntax can be combined according to (somewhat fragile) precedence rules, further abbreviating code.

  arc> (ssexpand 'f.x!y)
  ((f x) (quote y))
  arc> (ssexpand '~f:g)
  (compose (complement f) g)
  arc> (ssexpand 'f&g:h)
  (compose f&g h)
These expansions can be expanded even more since, e.g., compose and complement are macros. Indeed, ac.scm optimizes some away:

  ((compose f g) x)  ; ==> (f (g x))
  ((complement f) x) ; ==> (no (f x))
But there are some places ssyntax never expands.

  arc> (let (x y) '(1 2) (+ x y))
  arc> (let x.y '(1 2) (+ x y))
  Error: "reference to undefined identifier: _x"
I usually find myself writing the expanded expressions instead. So I got curious: how much of my code could use ssyntax? Despite the expansion tool, I had to look manually for opportunities to contract. "De-optimizing" can be non-obvious, precedence rules make it harder to combine ssyntax properly, and sometimes it just won't work.

But hey, that's what computers are for. So, I wrote sscontract.

  arc> (sscontract '(f x))
  arc> (sscontract '(f 'x))
  arc> (sscontract '(get x))
  arc> (sscontract '(get 'x))
  arc> (sscontract '(complement f))
  arc> (sscontract '(compose f g))
  arc> (sscontract '(andf f g))
Precedence rules?

  arc> (sscontract '(compose (andf f g) h))
  arc> (sscontract '(andf f (compose g h)))
  (andf f g:h)
  arc> (sscontract '(compose f ((x y) z)))
  arc> (sscontract '(andf f ((x y) z)))
  (andf f x.y.z)


  arc> (sscontract '(and ((andf f g) x) (a x) (b x)))
  arc> (sscontract '(no (f (g (h x)))))
  (~f:g:h x)
  arc> (sscontract '(map [_ 2] ((h 0) 0)))
  (map !2 h.0.0)

Special cases?

  arc> (sscontract '(assign (car a) b))
  (assign (car a) b)
  arc> (sscontract '(= (car a) b))
  (= car.a b)
  arc> (sscontract '(def f (x y) (x y)))
  (def f (x y) x.y)
  arc> (sscontract '(with ((x y) (f 5) z (g 10)) (+ x y z)))
  (with ((x y) f.5 z g.10) (+ x y z))

Simplifies code?

  ; Before

  (def all (test seq) 
    (~some (complement (testify test)) seq))

  ; After

  (def all (test seq)
    (~some ~testify.test seq))

  ; Before

  (def pair (xs (o f list))
    (if (no xs)
        (no (cdr xs))
         (list (list (car xs)))
        (cons (f (car xs) (cadr xs))
              (pair (cddr xs) f))))

  ; After

  (def pair (xs (o f list))
    (if no.xs
        (~cdr xs)
         (list:list:car xs)
        (cons (f car.xs cadr.xs)
              (pair cddr.xs f))))

  ; Before

  (def parse-format (str)
    (accum a
      (with (chars nil  i -1)
        (w/instring s str
          (whilet c (readc s)
            (case c 
              #\# (do (a (coerce (rev chars) 'string))
                      (wipe chars)
                      (a (read s)))
              #\~ (do (a (coerce (rev chars) 'string))
                      (wipe chars)
                      (readc s)
                      (a (list argsym (++ i))))
                  (push c chars))))
         (when chars
           (a (coerce (rev chars) 'string))))))

  ; After

  (def parse-format (str)
    (accum a
      (with (chars nil i -1)
        (w/instring s str
          (whilet c readc.s
            (case c
              #\# (do (a:coerce rev.chars 'string)
                      (a:read s))
              #\~ (do (a:coerce rev.chars 'string)
                      (a:list argsym ++.i))
                  (push c chars))))
          (when chars
            (a:coerce rev.chars 'string)))))
Well, it's for you to decide. If you don't like a contraction, you shouldn't use it. The aim is to produce a minimal (which is different from minimum) abbreviation of your code, erring towards correctness. This makes most macros' contractions boring, since quasiquote should mostly be left alone:

  arc> (sscontract '`(f x))
  (quasiquote (f x))              ; since `f.x != `(f x)
  arc> (sscontract '`(do ,(f x)))
  (quasiquote (do (unquote f.x))) ; since `(do ,f.x) == `(do ,(f x))
but maybe it's okay in your code to contract certain quasiquoted items. Or perhaps ssyntax makes the code less readable. It depends.

Still, it's useful to see how far ssyntax can go.

NOTE: After you put contract/ in your Arc directory, you'll want to

  $ mv ./contract/sscontract.arc .
Then just

  arc> (load "sscontract.arc")
  arc> (sscontract '(hello world))

2 points by aw 2619 days ago | link

Very cool. Thank you for this.

What is the difference between minimal and minimum in this context?

One of the things I like about Lisp is that it makes it easy (or at least easier) to write code transformation facilities like this.

I can imagine other code contraction facilities might be possible, giving suggestions of what might be options, even if they weren't correct all the time. I often notice a pattern in my code that I start to write a function or macro to extract, and then discover that it is already available in arc.arc.


1 point by fallintothis 2619 days ago | link

What is the difference between minimal and minimum in this context?

I basically wanted to preempt complaints about

  arc> (sscontract '(no (no (no (f x)))))
  (~no:~f x) ; equivalent to (~f x)
because I didn't bother finding a clean way to compress it. Quasiquotes are a better example. Even though something like

  (mac even (x) `(no (odd ,x)))
would be fine to define as

  (mac even (x) `(~odd ,x))
sscontract is better-safe-than-sorry:

  arc> (sscontract '(mac even (x) `(no (odd ,x))))
  (mac even (x) (quasiquote (no (odd (unquote x)))))
Note that this specific behavior can be changed; see special-cases.arc.

I can imagine other code contraction facilities might be possible

Good guess: that's my next project. ;)


2 points by conanite 2619 days ago | link

And beware of 'a&~b - it hangs (at least it did 132 days ago ... ), I don't know if this has been fixed.


1 point by fallintothis 2619 days ago | link

Yeah, it's still a problem. Oddly enough, I only ever noticed it in test code that expanded ssyntax tree-wise (vs. Arc's symbol-wise ssexpand). I didn't put deliberate thought into making sscontract aware of the bug, but I think it's avoided because of careful precedence separation.

  arc> (sscontract '(andf f (complement g)))
  (andf f ~g)
  arc> (sscontract '(compose f (complement g)))
  arc> (sscontract '(f ~g))
  (f:complement g) ; ew...should probably return (f ~g)
  arc> (sscontract '(f '~g))
  (f (quote ~g))
Thanks for the andf case, by the way. Hadn't thought of that one. If you find any incorrect contractions, please let me know. If you want, you can use bitbucket's issue tracker: