Arc Forumnew | comments | leaders | submitlogin
1 point by rocketnia 5065 days ago | link | parent

"the following bug"

Hey, that's not a bug, that's just metafns. :-p One of my mini-goals writing Penknife was to make metafns more consistent and user-definable, more like macros than special forms. Every syntax would return a "parse fork", which could expand in a few different ways based on being in functional position (metafns and macros), being in assignment position (setforms), etc. The infrastructure was there, and I used it to define ":", but I don't think I ever got around to making an in-language syntax for conveniently defining metafns.

Even if there were a convenient way to define metafns in Penknife, 'compose would still be a complex case: Given ((compose a b) c), we have to decide what data structure 'a receives as input. For maximum flexibility in Penknife, my choice was to define an all new data structure just for this purpose and extend the compiler to deal with it properly. On the downside, that's not a very brief way to go about it. XD

Anyway, with fexprs instead of a compilation phasse, metafns are streamlined already, since--as you've demonstrated--things can return fexprs.

I don't really know the Wart semantics, but here's my Kernel-like-pseudocode take on 'compose (if I choose not to make a separate expression type for the occasion):

  ; Given ((compose a b c) d e f), we evaluate
  ; ('<value of a> ('<value of b> ('<value of c> d e f))), using the
  ; binding of 'quote captured at compose's definition site (i.e., right
  ; here).
  (def compose ops
    (unless ops (err "Not enough operators to 'compose."))
    (let (last-op . ops) rev.ops
      (zap [map [list quote _] rev._] ops)
      (zap [list quote _] last-op)
      (vau args env
        (eval (foldr list ops (cons last-op args)) env))))
Here's a non-varargs version of the same thing:

  (def compose (a b)
    (zap [list quote _] a)
    (zap [list quote _] b)
    (vau args env
      (eval (list a (cons b args)) env)))
With your version, I'm a bit concerned about hygiene: What if the $f and $g expressions contain '$args? I think it could at least help to make 'compose a function rather than a macro, so that any preexisting instance of '$args is resolved outside your own '$args scope.


1 point by akkartik 5065 days ago | link

$args is an implicit gensym :)

"I think it could at least help to make 'compose a function rather than a macro, so that any preexisting instance of '$args is resolved outside your own '$args scope."

There's no difference between functions and macros in wart.

-----

2 points by rocketnia 5065 days ago | link

"$args is an implicit gensym :)"

I wasn't asking a question like that. :-p What I was asking was more along the lines of whether some expression like (compose foo $args) could end up with variable capture issues.

For all I know, maybe it wouldn't. Kernel's approach to evaluating an arg in the caller's scope is to pass scopes around as first-class values. Your approach is to change the scoping rules around in ways I don't really grok yet. Also, the way you're expanding $foo might be a kind of code-walking that essentially takes the form of its own kind of scoping rule.

-

"There's no difference between functions and macros in wart."

I think I already understand what you're trying to clarify here, too. What I'm saying is to use something other than "mac" to define 'compose.

If we forget about "functions" for a moment, what I mean is that instead of doing this...

  mac compose($f $g)
    `(lambda '$args
       eval `(,,$f (,,$g ,@$args)))
...it might be more hygienic to do something like this:

  mac compose($f $g)
    with (f eval.$f g eval.$g)  ; outside the scope of $args
      `(lambda '$args
         eval `(,,f (,,g ,@$args)))
I expect it's easier to make this change by replacing "mac" with "def":

  def compose(f g)
    lambda '$args
      eval `(,f (,g ,@$args))
(Keep in mind that this version still doesn't quote 'f and 'g, which I recommend doing in case they're lists or something.)

-----

2 points by akkartik 5065 days ago | link

Wow, you're right, compose doesn't need to be a macro!

Why was I thinking of it as a macro? Ah, I was blindly copying the arc version. And it looks like that doesn't need to be a macro either:

  (def compose fs
    (if (no cdr.fs)
      car.fs
      (fn args
        (car.fs (apply (apply compose cdr.fs) args)))))
Does that seem right? It seems to be working fine..

---

I still don't follow your arguments about hygiene. I suppose it's conceivable that code could use args2179 directly without going through gensym. Or that compose could call itself recursively and override $args in the caller's scope, and that it would be for some reason undesirable. Is either scenario what you were thinking of?

Thanks a lot for the comments. I think you understand the implications of my design decisions where I've been flailing around writing unit tests as I think up special-cases.

-----

1 point by rocketnia 5064 days ago | link

"Does that seem right?"

Yeah, I don't know why Arc's 'compose is a macro. I think I've tried to (apply compose ...) at least once and gotten bitten by that. ^^

---

"I still don't follow your arguments about hygiene. I suppose it's conceivable that code could use args2179 directly without going through gensym. Or that compose could call itself recursively and override $args in the caller's scope, and that it would be for some reason undesirable. Is either scenario what you were thinking of?"

I don't think so.... What I mean is, is it possible for the value inserted with ",$f" or ",$g" to contain the symbol '$args (assuming $ isn't a reader macro) in a way that causes it to be confused with the lambda's meaning of '$args?

  mac compose($f $g)
    `(lambda '$args
       eval `(,,$f (,,$g ,@$args)))

-----

1 point by akkartik 5063 days ago | link

Ah, I see. The answer is no, there can be no $vars after evaluation. $ is indeed expanded at read time.

-----

1 point by akkartik 5065 days ago | link

"(Keep in mind that this version still doesn't quote 'f and 'g, which I recommend doing in case they're lists or something.)"

You mean for something like:

  (compose '(1 2 3) *)

?

-----

1 point by rocketnia 5064 days ago | link

Yep. Here's a slightly-less-toy toy example:

  (let (foo nil bar nil)
    ; ...build foo and bar up as lists...
    ; ...
    (foo:- len.bar 1)
    ; ...
    )
If 'compose creates (<value of foo> (<value of -> len.bar 1)), then <value of foo>, being a list, might evaluate as an fexpr call.

-----

1 point by akkartik 5064 days ago | link

In wart fexprs either evaluate to a compiled op or to a list with the atom evald-lambda in the car.

You do raise the question of dealing with lists and hashes in function position. Wart doesn't do that yet, and it wasn't even on my todo list so far.

-----

1 point by rocketnia 5064 days ago | link

"You do raise the question of dealing with lists and hashes in function position. Wart doesn't do that yet, and it wasn't even on my todo list so far."

Oh, if you choose not to add that, I don't mind. ^_^

Still, as long as there's no quoting, I'm betting people can inject code in there, like (compose '$args '(while t)). Not that it's wrong to design 'compose with that code-injection feature in mind, but it would surprise me.

-----