Arc Forumnew | comments | leaders | submitlogin
A non-macro splice operator is superior to apply
2 points by akkartik 4330 days ago | 2 comments
I've mentioned before[1] that wart uses a @ operator instead of apply. Until now I considered the benefit to be syntactic uniformity with unquote-splice, the ability to splice anywhere in a call and not just right at the end, and the ability to make splice work in many macros[2].

  (foo x y @rest)      ; same as (apply foo x y rest)
  (foo @(list x y) z)  ; same as..?
Today I just realized a new benefit: the ability to splice in code into a call. For example, news.arc has a function called markdown with the following structure[3] for formatting asterisks as italics:

  (if
    ... ; lots of complicated cases

    (and (is (s i) #\*)
         (or ital
             (atend i s)
             (and (~whitec (s (+ i 1)))
                  (pos #\* s (+ i 1)))))
      ; then
      (do (pr (if ital "</i>" "<i>"))
          (zap no ital))

    ... ; lots more complicated cases
    )
The hubski codebase[4] wanted similar formatting for pluses into bold text. I'd like to extract out the repeated code into a function or macro but I can't, since it would have to return two expressions that are spliced into the caller. But in wart I could.

  def (format open close char|between s|at i flag)
    `((and (is (,s ,i) ,char)
           (or ,flag
               (atend ,i ,s)
               (and (~whitec (,s (+ ,i 1)))
                    (pos ,char ,s (+ ,i 1)))))
      ; then
      (do (pr (if ,flag ,close ,open))
          (zap no ,flag)))
(Not actually working wart code because I'm sacrificing correctness to keep the same vocabulary as the above arc fragment.)

Now the original structure becomes:

  (if
    ... ; lots of complicated cases
    @(format "<i>" "</i>" :between #\* :at s i ital)
    @(format "<b>" "</b>" :between #\+ :at s i bold)
    ... ; lots more complicated cases
    )
Can anyone think of a nice[5] way to use the code-generate the case without the @ operator? Here's a simpler working example you can try in wart:

  $ git clone git@github.com:akkartik/wart
  $ cd wart
  $ git checkout 43d9f79dc9
  $ ./wart
  ready! type in an expression, then hit enter twice. ctrl-d exits.
  <- x nil y 1
  (if x 34 y 35)
  => 35
  (if @`(,x ,34) y 35)
  => 35
Or is this example too contrived, and splicing multiple exprs into an if just weird? Why has lisp never felt the need for splicing somewhere other than the end of a call?

[1] http://www.arclanguage.org/item?id=15154

[2] As long as they're implemented using backquote: http://arclanguage.org/item?id=16378

[3] https://github.com/nex3/arc/blob/cb907aaea4/lib/app.arc#L433

[4] http://hubski.com; http://arclanguage.org/submitted?id=markkat

[5] Backquoting the entire outer if is the other option I can think of, but it doesn't seem very nice ^_^



2 points by rocketnia 4330 days ago | link

Your 'if example might not be contrived enough. :) Here's a way to do what you're doing in pg-Arc:

  (mac format (open close char s i flag)
    `(when (and (is (,s ,i) ,char)
                (or ,flag
                    (atend ,i ,s)
                    (and (~whitec (,s (+ ,i 1)))
                         (pos ,char ,s (+ ,i 1)))))
       (pr (if ,flag ,close ,open))
       (zap no ,flag)
       t))
  
  (if
    ... ; lots of complicated cases
    (format "<i>" "</i>" #;between #\* #;at s i ital)  nil
    (format "<b>" "</b>" #;between #\+ #;at s i bold)  nil
    ... ; lots more complicated cases
    )
A contrived 'obj example might be better....

  (if debug*
    (mac capture-debug-info ()
      '( debug        't
         dynamic-env  env
         line-num     line-num))
    (mac capture-debug-info ()
      '( debug        nil)))
  
  (obj @(capture-debug-info)
       type 'literal-expression
       val 36)
Here's one pg-Arc alternative, which is somewhat more verbose:

  (def obj+ args
    (w/table result
      (each arg args
        (each (k v) arg
          (= result.k v)))))
  
  (if debug*
    (mac capture-debug-info ()
      '(obj debug        't
            dynamic-env  env
            line-num     line-num))
    (mac capture-debug-info ()
      '(obj debug        nil)))
  
  (obj+ (capture-debug-info)
        (obj type 'literal-expression
             val 36))
Here's another, which is somewhat less flexible:

  (if debug*
    (mac capture-debug-info body
      `(obj debug        't
            dynamic-env  env
            line-num     line-num
            ,@body))
    (mac capture-debug-info body
      `(obj debug        nil
            ,@body)))
  
  (capture-debug-info
    type 'literal-expression
    val 36)

-----

1 point by akkartik 4329 days ago | link

Thanks for the new example! The if.. nil idea is cool; I wouldn't have thought of doing stuff in the conditions in a million years :)

-----