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

"(zap cdr foo)"

What's the advantage 'del gives me? Wouldn't my programs have fewer tokens if I directly used (zap cdr foo) instead of (del car.foo)?

Right now 'del looks like a smorgasbord of special-case utilities which could all have shorter tailor-made alternatives. Ideally what I like to see is a utility with an abstract enough description that I can build new tools upon it and straightforwardly extend it to make all my tools handle a new case. (Basically I have the same indifference to 'del as I have to 'coerce. ^_^ )

---

"The point of defining methods like `call` is that you don't call them directly"

Okay, I thought it might be something like that. ^_^ But in that case, it reminds me of Racket's structure type properties again. It's up to you to decide whether that's a flattering comparison or not, but you didn't seem to like them the last time.

---

"One idea would be that get-attribute would return a binded form, so you could just call (foo<-x ...) directly, without needing w/self[...]"

That's what I was intuitively thinking would happen. ^_^ I usually wouldn't say that, though. Perhaps I've been working in JavaScript for too long now. :-p

---

"[...]but still allow w/self to override it."

Er, even my currently weird intuition doesn't like that kind of inconsistency. >.>

What about making 'get-attribute a metafn? That is, ((get-attribute a b) ...) would expand into something like (invoke-attribute a b ...) the same way ((compose a b c) ...) expands into (a (b (c ...))).

Then when you want to call foo's x from bar, you say something like this:

  (w/self bar
    ((do foo<-x) ...))
The (do ...) would keep (get-attribute foo x) from being in functional position, so it wouldn't be treated as a metafn.

This would take a bit of language-core-hacking. Metafns are special-cased like special forms in official Arc--and even in your fork of ar, even though you're using gensyms for special forms. I would have tried to "fix" that in Arc a long time ago, but my new design for custom syntax was so different from Arc's that I considered it part of Penknife instead. The most straightforward way to get started in ar would probably be to design metafns as macros which expanded into literal macros, and then to let literal macros expand even when they're not in functional position.

---

At this point these are all just substanceless, brainstorming-like suggestions. I won't mind too much if you throw them all out, just as long as you've at least thought about them. ^_^



1 point by Pauan 4957 days ago | link

"What's the advantage 'del gives me? Wouldn't my programs have fewer tokens if I directly used (zap cdr foo) instead of (del car.foo)?"

Abstraction and readability. Let me answer your question with a question: why do we need (= (car foo) ...)? you can just use scar! Or why do we need (= (foo 'a) ...)? We can just use things like sref!

For the same reasons that overloading = to understand things is nice, overloading del to understand things is nice. So, if you feel that using (scar foo ...) is better than (= (car foo) ...), then by all means use (zap cdr foo), but I personally like using the = forms, and so I like del for the same reasons.

I'll note that there is a defdel, which lets you create new "del" stuff, similar to defset. And you can extend dref as well, just like extending sref. It's basically the exact same idea as =, except it's applying to deletion rather than assignment.

And let me say one more thing. How would you easily and concisely implement this, which deletes a range of elements:

  (= foo '(1 2 3 4))

  (del (foo 1 2)) -> (1 4)
Or how about doing it in steps, so you could delete, say, every other element in a list:

  (= foo '(1 2 3 4 5 6 7))

  (del (foo 0 nil 2)) -> (1 3 5 7)
Sure, you could write a special-case function for that, in the same way we have special-case functions like scar, scdr, and sref, but it's nice to be able to bundle them up into a single convenient macro, like del.

---

"It's up to you to decide whether that's a flattering comparison or not, but you didn't seem to like them the last time."

I didn't like how they automatically created stuff like exn-message, rather than letting you do (foo 'message). That's different from disliking the overall idea.

---

"Perhaps I've been working in JavaScript for too long now. :-p"

JavaScript does have bind... :P

  foo.bind(this)
---

"This would take a bit of language-core-hacking."

Or I could just use dynamic variables, which is what I'm doing. It's quite simple: if self is bound, use that, otherwise use the object. Basically, self defaults to the current object, while letting you override it with w/self.

This is actually very similar to JavaScript:

  foo.method(...)
  foo.method.call(bar, ...)
Which would be like this, with my library:

  (foo<-method ...)
  (w/self bar (foo<-method ...))
The difference is, that in JS they use the read-only "this" keyword, but in my library I'm just using an ordinary dynamic variable.

-----

1 point by rocketnia 4957 days ago | link

"Let me answer your question with a question: why do we need (= (car foo) ...)? you can just use scar! Or why do we need (= (foo 'a) ...)? We can just use things like hash-set!"

I was originally going to use that as an example. ^_^

Arc's notion of assignment follows the abstract, informal contract that (= (foo ...) bar) changes whatever (foo ...) returns. This means we can have utilities like 'zap and 'swap that treat something as both a "get"-like expression and a "set"-like expression. I don't see 'del having a contract like that, so I don't see why "it's nice to be able to bundle them up."

I'd implement your range deletion examples like this...

  (mac del (place start (o howmany 1) (o step 1))
    ...)
...and say (del foo 1 2) and (del foo 0 nil 2).

---

"I didn't like how they automatically created stuff like exn-message, rather than letting you do (foo 'message). That's different from disliking the overall idea."

Ah, okay. That works for me.

---

"JavaScript does have bind... :P"

I'm already fully aware a JavaScript programmer can control the value of "this" using call, apply, or bind. ^_^

All I'm talking about is the way that foo.bar( baz ) calls the value of foo.bar with foo as "this," but if you instead define idfn() and say idfn( foo.bar )( baz ), then "this" is the root object. That's not the most elegant semantics in the world, but I'm saying my intuition is somehow in line with it right now.

(The "root object" aspect isn't part of my intuition, though. I'm fine with idfn( foo.bar )( baz ) using anything for "this", including treating it as a dynamic variable like you are.)

---

"Or I could just use dynamic variables, which is what I'm doing."

Again, I was already taking that for granted and not arguing with it. Instead, I was talking about a way for (foo<-x ...) to automatically use (w/self foo ...), the way JavaScript foo.x( ... ) is like foo.x.call( foo, ... ).

---

"It's quite simple: if self is bound, use that, otherwise use the object."

That's not reassuring....

I'm particularly concerned about the case where we're using (foo<-method ...) from inside another object's method. I'm afraid the self variable will already be bound then, so instead of calling the method on foo, we'll end up calling it on the current object. As a workaround, we'll have to say (w/self foo (foo<-method ...)).

I guess we could probably say (w/self nil (foo<-method ...)) too, but that's still not ideal.

-----

1 point by Pauan 4957 days ago | link

"Arc's notion of assignment follows the abstract, informal contract that (= (foo ...) bar) changes whatever (foo ...) returns."

del's contract is that it zaps the variable to whatever dref returns. It is more analogous to = than you think. In fact, it's exactly the same idea, only the name is different, and it's purpose is different.

So, my point still stands. If you think that (= (car foo) ...) is good, then why is (del (car foo)) bad? You may think, "well, I don't think that's useful". Fine. There's plenty of Arc functions I don't think are useful. Just don't use del, then.

But in the same way that = being general-purpose is obviously useful, del being general purpose is obviously useful. For instance, how do you delete an attribute?

  (del foo<-bar)
Compared to:

  (del-attribute foo 'bar)
How do you define a custom deletion behavior for your object? Just slap on a `del` attribute, in exactly the way that objects can define a `set` attribute.

Did you define new functions "get-foo", "set-foo" and "del-foo"? Well, nobody has to know about "del-foo" because you can just use defdel, in the same way that nobody needs to know about scar, because you can just use (= (car ...) ...)

And nobody has to know about set-foo, because you can use defset... and nobody has to know about get-foo, because you can use defcall. Notice the pattern, here?

---

"The specific case I'm interested in is the one where we're using (foo<-method ...) from inside another object's method. I'm afraid the self variable will already be bound then, so instead of calling the method on foo, we'll end up calling it on the current object."

Ah yes, good point. I should have a unit test for that.

-----

1 point by akkartik 4957 days ago | link

"It is more analogous to = than you think. In fact, it's exactly the same idea, only the name is different, and it's purpose is different."

If the name and purpose are different what's left? :p

Slices and steps don't currently work with '=. So they feel bolted on to del.

There's two separate issues here: whether we need del in addition to '=, and whether places should support slices and steps. Let's discuss them separately.

-----

1 point by Pauan 4957 days ago | link

"There's two separate issues here: whether we need del in addition to '=, and whether places should support slices and steps. Let's discuss them separately."

Of course. I don't recall ever conflating the two, and I think we should allow for both assigning to slices, and deleting slices. In fact, then deletion could use assignment, so this:

  (del (foo 0 1))
Would expand to this:

  (= (foo 0 1) nil)

-----

1 point by akkartik 4957 days ago | link

  (= (foo 0 1) nil)
Incidentally, I just remembered that there's already a name for assigning to nil: wipe. May still make sense to have both names, though.

-----

1 point by Pauan 4957 days ago | link

Yes, except we would need to change sref so it understands slices. Which I think we should do.

-----

2 points by rocketnia 4957 days ago | link

I don't. We already have (= (foo 0) ...) modifying the first element, so we don't have room for it to modify the slice 0..<(0 + 1). We already have (zap inc (my-table idx 0)) incrementing my-table.idx with a default starting value of 0, so we don't have room for it to modify the slice idx..<(idx + 0).

(FYI: The ..< I'm using is Groovy's notation for half-inclusive ranges, inspired by Ruby's ... notation but less ambiguous with Groovy's other syntax. I also prefer it for its obvious asymmetry.)

It would probably make more sense to use (slice foo ...) to get a slice and (= (slice foo ...) ...) to put it back.

This isn't ideal, as far as I'm concerned, 'cause it means we don't always get what we put in:

  arc> (= foo '(a b c d e f g))
  (a b c d e f g)
  arc> (= (slice foo 1 3) '(x y))
  (x y)
  arc> foo
  (a x y e f g)
  arc> (slice foo 1 3)
  (x y e)
However, that's not really part of the existing contract:

  arc> (= foo (table))
  #hash()
  arc> (= (foo 'a 1) nil)
  nil
  arc> foo
  #hash()
  arc> (foo 'a 1)
  1
Furthermore, I don't know a place I'd use that contract even if it did exist. ^_^ So I retract my objections for now, just as long as slice-getting isn't ambiguous.

-----

1 point by Pauan 4957 days ago | link

How would it be ambiguous? (foo 0 1) returns a list of the first element. (foo 0 nil) returns the entire list, and those would work in assignment too:

  (= (foo 0 1)   '(3 4))   ; splice a list into the first element
  (= (foo 0 nil) '(1 2 3)) ; replace the list with a different one
Hm... why not use cut rather than slice?

  (= (cut foo 0 1) '(3 4))
I mean, cut already exists, so...

-----

2 points by rocketnia 4957 days ago | link

"(foo 0 1) returns a list of the first element."

In tables, (foo 0 1) already means the same thing as (or foo.0 1). But hmm, maybe you're not interested in slicing tables. That's fine, then. ^_^

By the way, newLISP does something like that with (0 1 foo): http://www.newlisp.org/downloads/newlisp_manual.html#implici...

The other issue was that I think at some point you were using (foo 0) to mean a one-element slice, and that would definitely be ambiguous with regular old indexing.

---

"Hm... why not use cut rather than slice?"

I was going to say that, but I didn't want to clutter my point too much. Arc's 'cut accepts start and stop positions, and your slicing examples instead use a start position and a length. Translating between those was making my post harder to follow (I think), so I just assumed the definition of 'slice instead. Either should work. ^_^

-----

1 point by Pauan 4957 days ago | link

"In tables, (foo 0 1) already means the same thing as (or foo.0 1). But hmm, maybe you're not interested in slicing tables. That's fine, then. ^_^"

What would a table slice even mean? They're not even ordered. The closest thing would be a table that has a subset of the keys of another table, but that wouldn't use numeric indexing.

---

"The other issue was that I think at some point you were using (foo 0) to mean a one-element slice, and that would definitely be ambiguous with regular old indexing."

I was? (foo 0) should always be an element, not a slice. Ah well, it doesn't matter. If I did use that, I was incorrect.

I'll note that (del (foo 0)) is equivalent to (= (foo 0 1) nil) so perhaps that confused you.

---

"Arc's 'cut accepts start and stop positions, and your slicing examples instead use a start position and a length."

They did? I always thought of it as being a start/end position, like cut. So for instance:

  (= foo '(1 2 3 4 5 6))

  (= (foo 2 5) nil)

  foo -> (1 2 6)
The way I'm doing it is, the number of elements is the end minus the start, so for instance, in the example above, 5 - 2 is 3, so it removed 3 elements, starting at position 2. This is exactly how cut behaves:

  (= foo '(1 2 3 4 5 6))

  (cut foo 2 5) -> (3 4 5)
In fact, I think it'd be a good idea for lists/strings in functional position to just delegate to cut, so (foo 2 5) would be the same as (cut foo 2 5)

-----

2 points by rocketnia 4957 days ago | link

"What would a table slice even mean ?"

Hence my "but hmm." ^_^

---

"I'll note that (del (foo 0)) is equivalent to (= (foo 0 1) nil) so perhaps that confused you."

Yeah, that's probably it.

---

"I always thought of it as being a start/end position, like cut."

It's hard to tell because you use 0 as the start pretty often, but here are the two examples I was going off of:

From http://arclanguage.org/item?id=14656:

  (= foo '(1 2 3 4 5 6 7))

  (del (foo 1 4)) -> (1 6 7)
From http://arclanguage.org/item?id=14660:

  (= foo '(1 2 3 4))

  (del (foo 1 2)) -> (1 4)
Guess those were buggy. Well, now I know. :-p

-----

1 point by Pauan 4957 days ago | link

"Guess those were buggy. Well, now I know. :-p"

Ah, yeah, I switched from inclusive to half-open. And since I just so happened to use 1 as the start index, you couldn't really tell.

-----

1 point by akkartik 4957 days ago | link

What would a table slice even mean?

A sub-table with just the given keys?

-----

1 point by Pauan 4957 days ago | link

Well, sure, but you wouldn't use numeric indices to refer to that. :P It might be something like this:

  (subset x 'foo 'bar 'qux)
As opposed to:

  (x 0 1)
Which would be slice notation, which would work only on ordered sequences, like lists.

Which isn't to say that table subsets would be a bad idea, just that it's a different idea from list slicing, so there shouldn't be any ambiguities with slice notation (which was one of rocketnia's concerns).

-----

1 point by akkartik 4957 days ago | link

You could implement subsetting at function position, in which case it would be ambiguous. I already use :default in wart, so I don't care too much about that :) But yeah I hadn't thought of how to create ranges of keys. This may not be a good idea. You can't distinguish table lookup from a subset table with just one key/val, for example.

-----

1 point by Pauan 4957 days ago | link

Well, we could define it so table slicing would only happen with 3 or more arguments:

  (foo 'a 'b 'c)
...but I would consider that to be confusing, and would suggest an explicit function like "slice" or "subset".

-----

1 point by akkartik 4957 days ago | link

Yeah that's my sole feedback on del at this point: put the smarts into sref and defcall.

I have no idea how to do that :)

-----

1 point by Pauan 4957 days ago | link

Well then, guess I'll go in and do it, as a library for starters, and then we can potentially get it into base ar later.

By the way, sref has a terrible argument signature:

  (sref foo value key)
Intuitively, it really should be[1]:

  (sref foo key value)
But that's a backwards-incompatible change... Also, setforms only sends a single argument to sref, so we would need to change that too. For instance:

  (= (foo 0 1) 'bar)
Is translated into:

  (sref foo 'bar 0)
But it should be:

  (sref foo 'bar 0 1)
But if I change that, it breaks other things. Ugh. This really wasn't designed with slices in mind.

---

* [1]: To be fair, having the key as the last argument is great for slices, but bad for pretty much everything else. The argument order would make a lot more sense if sref had been designed with slice support.

-----

1 point by rocketnia 4957 days ago | link

"Also, setforms only sends a single argument to sref, so we would need to change that too."

Yeah, I think I noticed that at one point a long time ago, and I consider it to be a bug. (I promptly forgot about it. XD;; ) I never rely on that behavior, but then I don't extend things in the Arc core much either, for cross-implementation compatibility's sake.

---

"To be fair, having the key as the last argument is great for slices"

I think that's the reason it is the way it is. I've ended up designing some of my utilities the same way. For instance, I usually give failcall the signature (func fail . args), which is very similar to sref's (func new-value . args). I'd probably call 'sref "setcall" if I were to reinvent it.

I'm not sure I want to reinvent 'sref though: It's kinda bad as a failcallable rulebook, because you can't tell if it will succeed until you've called it, and then you get side effects. I've been thinking about other designs, like a currying-style ((sref func args...) new-value) version or something. But this is kinda specific to a system that uses failcall. In Arc, we can't really recover when an extension doesn't exist, so we don't write code that tries yet.

-----

1 point by Pauan 4957 days ago | link

Oh! I just found a bug! In arc.arc:

  (= (sig 'msec nil))
Should be:

  (= (sig 'msec))
Right? And here too...

  (= (sig 'seconds nil))
Should be:

  (= (sig 'seconds))
Okay, this is too much of a coincidence. aw, is there a reason for this, or was it just a mistake that we can safely fix?

-----

1 point by akkartik 4957 days ago | link

"Right now 'del looks like a smorgasbord of special-case utilities which could all have shorter tailor-made alternatives."

That was my first reaction as well. I see one general new idea here, though: to extend indexing to return not just places but slices like in python and Go.

  (= l '(1 2 3 4))
  (l 1) ; 2
  (l 0 2) ; '(1 2)
  (= (l 0 2) '(3)) ; l is now '(3 3 4)
Now del becomes identical to setting a range to nil.

  (= l '(1 2 3 4))
  (= l.0 nil) ; l is now (nil 2 3 4)
  (= (l 0 1) nil) ; l is now (2 3 4)
Would this be useful?

-----

1 point by Pauan 4957 days ago | link

"Would this be useful?"

It is in Python. Would it be useful in Arc? We already have cut, which does the same thing:

  (cut '(1 2 3 4) 0 2) -> (1 2)
But having a shorter syntax is nice. Would it be useful to assign to it? I think so, for the same reasons that it's nice in Python.

-----

1 point by akkartik 4957 days ago | link

"It is [useful] in Python."

Python doesn't allow assigning to slices, I think.

-----

1 point by Pauan 4957 days ago | link

It absolutely does! It also allows for deleting indices/slices:

  foo = [1, 2, 3, 4]
  foo[0:2] = [0]
  foo -> [0, 3, 4]

  del foo[0:1]
  foo -> [3, 4]
In fact, there's an idiomatic way to delete every element in a list:

  del foo[:]
  foo -> []

-----

2 points by akkartik 4957 days ago | link

Ah.

And del is identical to assigning to [].

  >>> foo = [0, 1, 2]
  >>> foo[0:1] = []
  >>> foo
  [1, 2]
I stand corrected.

-----