Arc Forumnew | comments | leaders | submitlogin
You can eat your ssyntax and have it, too: custom macro-specific ssyntaxes
4 points by conanite 5576 days ago | 4 comments
Arc expands macros before ssyntax. Your macros' arguments may be or include unexpanded ssyntax. As the macro writer, you can decide what to do with these symbols, and you don't have to leave it to ac to decide what to do.

Given that every solution must have a great problem, here's an experiment with that idea:

  arc> (dfn some (testify:test seq)
         (if (alist seq)
             (reclist test:car seq)
             (recstring test:seq seq)))

  arc> (some 'z '(a b c d e))
  t
  arc> (some 'z '(a b c d e))
  nil
  arc> (some [isa _ 'sym] '(a b c))
  t
  arc> (some [isa _ 'int] '(a b c))
  nil
This 'some is equivalent to the original from arc.arc, and I believe it wins for readability:

  (def some (test seq)
    (let f (testify test)
      (if (alist seq)
          (reclist f:car seq)
          (recstring f:seq seq))))
This isn't a world changing idea, but (-- tokens) and there are times when it's useful to pass your arguments through some kind of a filter. There are at least 12 other functions that use testify in this way in arc.arc and strings.arc. But regardless of whether this particular example excites you, I'm sure there are many other great ways to exploit custom macro-specific ssyntax.

Here's a quick and dirty implementation, I'm sure it could be improved to effect a net decrease in total token count. Disclaimer: only lightly tested (scheme & rainbow).

  (mac dfn (name params . body)
    `(= ,name (fn ,(dfn-params params)
                (with ,(dfn-withses params)
                  ,@body))))

  (def dfn-p-mapper (p)
    (if (acons p) (map2 dfn-p-mapper p)
                  (let toks (tokens (coerce p 'string) #\:)
                    (if cdr.toks
                        (if (is car.toks "?")
                            `(o ,(sym:cadr toks))
                            (sym:cadr toks))
                        p))))

  (def dfn-params (params)
    (map2 dfn-p-mapper params))

  (def dfn-param (p)
    (let toks (tokens (coerce p 'string) #\:)
      (if (and cdr.toks (isnt car.toks "?"))
          `(,(sym:cadr toks) (,(sym:car toks) ,(sym:cadr toks))))))

  (def dfn-withses (params)
    (mappend idfn (accum x
      (afnwith (p params)
        (if (no p)   nil
            (atom p) (aif dfn-param.p x.it)
                     (do (self:car p) (self:cdr p)))))))

  ; like map1, but handles improper lists
  (def map2 (f xs)
    (if (no xs)   nil
        (atom xs) (f xs)
                  (cons (f:car xs) (map2 f cdr.xs))))
Note that dfn uses '= to assign the function, so you can use any expression for which a setter is defined, not just a symbol. Here are some test cases:

  ; use ?:x instead of (o x nil)
  (dfn dfn-test (string:foo ?:bar)
    (pr "foo is a " type.foo " and bar is " bar))

  ; works for rest params too
  (dfn dfn-test2 (string:foo . len:bar)
    (pr "foo is a " type.foo " and there are " bar " bars"))

  ; identical to def if there is no param ssyntax
  (dfn dfn-test3 (foo . bar)
    (pr "foo is a " type.foo " and bar is " bar))

  (suite "dfn: a superior def"
    ("use ?:x instead of (o x nil)"
      (tostring:dfn-test 'hello)
      "foo is a string and bar is nil")

    ("? for optionals: given"
      (tostring:dfn-test 'hello 1234)
      "foo is a string and bar is 1234")

    ("handles rest param"
      (tostring:dfn-test2 'resting 'a 'b 'c 'd)
      "foo is a string and there are 4 bars")

    ("without ssyntax is identical to def"
      (tostring:dfn-test3 'a 'b 'c 'd)
      "foo is a sym and bar is (b c d)"))


4 points by rntz 5575 days ago | link

Cute, but what if I wanted to use a non-atomic expression as the transforming function, or wanted to destructure the result of the function application? For example:

  ; this won't work, because it's not intra-symbol
  arc> (dfn head ([coerce _ 'cons]:(h . tl)) h)
  #<procedure: head>
  arc> (head "foo")
  #\f
IMO, it's generally bad to use ssyntax as anything other than an abbreviation, rather than true syntax, because as true syntax it is somewhat lacking: it can only be used as a unary or binary operator on atoms, and atoms are severely lacking in expressivity. Hence the dual nature of lisp code, which is made up of both atoms and lists. Of course, this doesn't eliminate the usefulness of macro-specific ssyntax in general, but it does make applying it "properly" (ie: as an abbreviation only) much more tedious.

I also have other reasons I dislike the notion of macro-specific ssyntax. It makes lisp code less homogenous, requires the user to remember more, and most of all binds us to the notion of ssyntax, which I find ugly in the first place - I'd rather have real syntax, implemented in the reader as opposed to the compiler/macroexpander. That way I could use the syntax anywhere with uniform meaning, and always as an abbreviation for something that I could write more explicitly.

-----

1 point by conanite 5575 days ago | link

  [coerce _ 'cons]:(h . tl)
but ssyntax doesn't do this anyway!

I'm not sure I understand your point about abbreviation; in my example isn't the ssyntax an abbreviation for a let form within the function?

The point you make about homogeneity is important - so maybe the example should be rewritten like this:

  (dfn some (testify.test seq)
    (if (alist seq)
        (reclist test:car seq)
        (recstring test:seq seq)))
The dot ssyntax in this case is just what the caller would have to do if the callee expected a pre-testified arg:

  (some testify.ch chars)
So the choice of dot instead of colon might be better for consistency - it looks like it's just inlining existing code.

I like it less this way, personally - it looks like "testify" is somehow part of the param name, whereas with "testify:test" the two parts seem more distinct, that "testify" is something you apply to "test" before proceeding with the function. But maybe that's just subjective.

-----

2 points by palsecam 5556 days ago | link

Very interesting.

> it's useful to pass your arguments through some kind of a filter

A quite common pattern, at least in my programming experience.

However, like rntz, I'm not sure using ssyntax to do that is a Good Thing. But what about a special form "c"(check) like "o"(ptional)?

  arc> (def2 some ((c test testify) seq)
         (if (alist seq)
             (reclist test:car seq)
             (recstring test:seq seq)))
The first arg to "c" would be passed to the second arg, and the result of it would be bound back to the first arg. Or, if there an error, well, throw it.

----

Other notes: I hate to freely criticize, but the defs of 'some, 'find and co are sooooo ugly because of the string/list dichotomy. Not clean for a "code is the spec"-based language. And not extensible. Arc has a problem w/ the overloading issue. See also: 'map doesn't work for tables, but 'each does (my and some news.arc code would benefit from 'map working w/ tables [currently it uses 'accum & 'maptable]). See also: the def of 'each is also ugly/unextensible. See also: queues and the functions to manipulate them, like 'qlen, that have ugly names because you can't add "methods" to 'len. See also: 'rev doesn't work for strings?!!

'testify... Idea in the wild: make (coerce anAtom 'fn) do what 'testify does.

-----

1 point by conanite 5575 days ago | link

oops, typo in copy/paste from console: should read

  arc> (some 'a '(a b c d e)) ; <-- 'a not 'z !
  t
  arc> (some 'z '(a b c d e))
  nil

-----