Arc Forumnew | comments | leaders | submitlogin
1 point by akkartik 4446 days ago | link | parent

"it evaluates its arguments, so you have to double-quote them"

But the whole point is that the list is just a list of elements! Imagine it's coming from a whole complex sequence of functions. Don't think of it like this:

  (all-true '((1 2))
Think of it like this:

  (all-true l) ; with l bound to ((1 2))
You want callers of all-true to take the trouble to quote elements of l, to be aware that it's going to go through all-true. Isn't that simply hideous? What if they then have to go through a different path that doesn't eventually pass through a macro? Do they have to recompute l without quotes?

"that's completely incorrect because you always always have to know whether it's a function or a macro, even in a language like Arc or Scheme"

What I called the 'point of lisp' was my sense of the ideal. The promise of a high-level language is that you can create new names and use them like primitives that the language came with.

  (let x 3
    bar.x) ; Does the language come with let? Who cares?
All languages break this abstraction, yes. But lisp is lisp because it takes this abstraction farther than anything else.

You've explained the technical reasons why 'apply for macros' is hard, and that makes sense. But the solution is to not use apply with macros, not tie ourselves up in knots with quote.



1 point by Pauan 4446 days ago | link

"Think of it like this: (all-true l)"

Sure, I already considered that. That is, after all, the most common use for apply. And I agree that's ugly. You'd have to say something like (all-true (wrap-quote l)) or somesuch. Ugly.

---

"Isn't that simply hideous?"

Sure is!

---

"The promise of a high-level language is that you can create new names and use them like primitives that the language came with."

And fexprs/macros let you do that. That wasn't my point. What you said is that the point of macros is being able to call things without caring whether the thing is a macro/function/whatever. That view is incorrect because the meaning of the expression differs depending on whether it's a function or macro.

For instance, in the "let" expression you just gave, it doesn't evaluate the variable "x", but you only know that because "let" is a macro. If it were a function it would throw a "x is undefined" error. So you have to know whether something is a function or macro in order to use it correctly.

The same is true of apply: it has two different (and inconsistent) behaviors depending on whether its argument is a fexpr or a function. And that's a problem because it's confusing and makes it really easy to get it wrong. Hence why Kernel makes you explicitly use wrap to specify your intent.

---

"You've explained the technical reasons why 'apply for macros' is hard, and that makes sense. But the solution is to not use apply with macros, not tie ourselves up in knots with quote."

Sure, and even with that approach, if your language has wrap/unwrap, then you can still use apply on macros, you just need to wrap them first:

  (apply (wrap foo) ...)
And if that breaks, then you bring out eval:

  (eval `(,foo ...))

-----

1 point by akkartik 4446 days ago | link

"For instance, in the "let" expression you just gave, it doesn't evaluate the variable "x", but you only know that because "let" is a macro. If it were a function it would throw a "x is undefined" error."

No, I thought about that when I built let. And then I forgot about it. Now when I see that expression I think, "oh, I'm creating a binding." The point of having power is to not have to derive things from first principles every time we see them.

"And fexprs/macros let you do that. That wasn't my point. What you said is that you want to be able to call things without caring whether the thing is a macro/function/whatever."

Yeah, I'm still struggling to express what I mean by 'use them like language primitives'. I think I'm trying to invoke a sense of fundamental knowledge. When I was taught lisp I didn't think, "wait how does (setq a 3) work if a is not bound". I thought, "Ok, setq is assignment." Functions, macros, fexprs, these are all tools to setup such axiomatic intuitions. That's the whole point.

-----

1 point by Pauan 4446 days ago | link

"No, I thought about that when I built let. And then I forgot about it."

Yes, but you still need to carry around that knowledge in your subconscious, and retrieve that knowledge every time you use "let". That was my point: that the distinction between them is important.

---

"The point of having power is to not have to derive things from first principles every time we see them."

Yes... and I'm agreeing with you.

You said that the point of macros is not having to care whether something is a function/macro or not. But on a very fundamental level you do have to care, just like how you have to care about whether a variable is a string or a number.

I was only disagreeing with that statement, that you don't need to care about the differences between fexprs/functions. All the problems with apply come about because of misunderstandings of the differences between the two:

  ;; functions
  (foo '1 '(2 3))
  (apply foo '(1 (2 3)))

  ;; fexprs
  (foo 1 (2 3))
  (apply foo '(1 (2 3)))
This difference is what causes the confusion and makes it very easy to make mistakes when using apply on fexprs. So it's not so much that apply doesn't work on fexprs, as it is that the behavior of apply for fexprs is different than the behavior of apply for functions... which is awfully confusing and inconsistent.

So choosing to make apply work only on functions is a perfectly reasonable position that discourages mistakes.

-----

1 point by akkartik 4446 days ago | link

"And if that breaks, then you bring out eval"

  wart> = l '((1 2))
  wart> (and @l) ; error
  wart> (wrap.and @l) ; error
  wart> (eval `(,wrap.and ,@l)) ; error
  wart> (eval `(,wrap.and ,@(map (fn(_) (cons quote _)) l))) ; error
I think our aesthetics are in disagreement :) When the second one (or maybe the third one, in extreme extremis) fails, I would simply go back and rewrite all-true to not use apply/splice, like I did all: http://github.com/akkartik/wart/commit/d25410707d

-----

1 point by Pauan 4446 days ago | link

Er, shouldn't it be (list quote _) ...? In any case, you can wrap up the last one into a function:

  (def operate (f args)
    (eval `(,f ,@(map (fn (_) (list quote _)) args))))
Then it would just be (operate and l)

I'm not saying that's necessarily a good idea, but if it comes up often enough, might as well write a helper function for it.

---

And when I said "bring out eval" I meant like this:

  (eval `(,and ,@l))
In other words, not wrapping it.

-----

2 points by akkartik 4446 days ago | link

"In other words, not wrapping it."

Ah, this works :)

  (eval `(,and ,@(map (fn(_) (cons quote _)) l)))
In wart, 'a is just (' . a). That eliminates questions like, "what happens if quote gets multiple args?" :)

-----

1 point by akkartik 4446 days ago | link

Pauan, I'm starting to think it's a win to define mapquote like you suggested:

  def all-true(xs)
    (and @mapquote.xs)
But using quoting to suppress future eval like this makes me nervous. Do you think it's a correct transformation?

-----

1 point by Pauan 4446 days ago | link

My position is quite simple: if you want clean elegance, just use Kernel. Seriously.

If that definition of all-true works and doesn't have any noticeable bugs... then go ahead and use it. It'll be hacky and won't be clean and elegant, but it'll be short and simple and work. And it seems to me that wart is trying to get the power of fexprs in a more hacky Arc-ish way, so that's perfectly fine.

-----

1 point by Pauan 4446 days ago | link

Ah, like PicoLisp. Neat, I like that.

-----

1 point by akkartik 4446 days ago | link

  > > Isn't that simply hideous?
  >
  > Sure is!
I think we're in complete agreement that apply for macros is not well-behaved. The rest is just splitting hairs :)

-----

2 points by Pauan 4446 days ago | link

"I think we're in complete agreement that apply for macros is not well-behaved."

Yes, and as I already said a while ago, I think Kernel's approach is ideal. Except now I actually have some solid reasons to back up that statement, thanks to you.

-----