Arc Forumnew | comments | leaders | submitlogin
Should copy go deep?
2 points by akkartik 4181 days ago | 6 comments
I just noticed that arc's copy on lists doesn't create copies of the car. So lists of lists are only shallow-copied.

  ; called from copy when xs is a list
  (def copylist (xs)
    (if (no xs)
        nil
        (cons (car xs) (copylist (cdr xs)))))
Does this seem like the right semantic for copy? I'm inclined to say no, that copy should be powerful at the price of performance. What do y'all think?

The drawback of a deeper copy is that the above function needs to be inlined into arc's list function, which is currently:

  (def list args
    (copylist args))
I'll update anarki if there's no objections.

---

Incidentally, the definition of copy is a great illustration of wart's strengths. This:

  (def copy (x . args)
    (let x2 (case (type x)
              sym    x
              cons   (copylist x)
              string (let new (newstring (len x))
                       (forlen i x
                         (= (new i) (x i)))
                       new)
              table  (let new (table)
                       (each (k v) x
                         (= (new k) v))
                       new)
                     (err "Can't copy " x))
      (map (fn ((k v)) (= (x2 k) v))
           (pair args))
      x2))
Becomes this:

  # in list.wart
  def (copy x)
    if ~cons?.x
      x
      (cons copy:car.x copy:cdr.x)

  # in table.wart
  def (copy x) :case table?.x
    (as table (as list x))

  def (copy x ... merge_args) :case merge_args
    ret result copy.x
      each (k v) pair.merge_args
        result.k <- v
It's about the same length, but various concerns are disentangled. It does something reasonable for every type, including numbers and user-defined types.

I just noticed today that arc's copy had that additional semantic of merging in new args, and it turned out to be easy to add. Can Nulan add it cleanly? :) (Ignoring whether it's a good idea or not.)



3 points by Pauan 4181 days ago | link

"What do y'all think?"

I think conses are silly and Lisps should use objects instead!

If you insist on using cons, at least make everything immutable, in which case you don't even need copy at all. Yup. That's my stance: accidentally immutable all the things.

---

"The drawback of a deeper copy is that the above function needs to be inlined into arc's list function, which is currently [...]"

pg actually says in the arc.arc source code:

  ; Can return to this def once Rtm gets ac to make all rest args
  ; nil-terminated lists.

  ; (def list args args)
That definition works just fine in Arc/Nu. If it doesn't work right in Anarki, I'd say that's a bug with their compiler that should be fixed. As an added bonus, in Arc/Nu, the above is over 3 times faster than Arc's definition of copylist.

---

"I just noticed today that arc's copy had that additional semantic of merging in new args, and it turned out to be easy to add. Can Nulan add it cleanly? :)"

I'm not sure what you're talking about. Could you clarify please?

-----

1 point by akkartik 4181 days ago | link

I meant the last definition:

  def (copy x ... merge_args) :case merge_args
    ret result copy.x
      each (k v) pair.merge_args
        result.k <- v
I assume Nulan's way of doing extensible copy would be to call an object's %copy method or something like that. But would it be able to handle if the signature for copy changed, or would you have to go update all the %copy methods?

-----

3 points by Pauan 4181 days ago | link

"I assume Nulan's way of doing extensible copy would be to call an object's %copy method or something like that. But would it be able to handle if the signature for copy changed, or would you have to go update all the %copy methods?"

Well, there's no need for copy since everything's immutable in Nulan... but assuming I did want copy, yes there would be a %copy method for objects. And yes, if you changed the signature for the %copy method you'd need to change all the objects.

That's because methods form an interface: if you fulfill the interface, you get the functionality. So naturally if the interface changes, any code that uses that interface will need to change. If you're really worried about backwards compat, you could instead create a new %copy2 interface, etc.

But the copy function itself is not the same as the %copy method. So even if the copy function changed, it might not require changes in the %copy method. It depends on what the changes are.

Looking at that function... it looks like you're saying that this:

  (copy x 'a 5 'b 10)
Would copy "x", and then assign "a" to 5 and "b" to 10, correct? That change would be trivial to do in Nulan, without affecting the %copy method at all. I guess it'd look something like this:

  (def copy -> (let x [ %copy f ]) @r
    (let x (f x)
      (each (pair r) -> [k v]
        (set! x k v))
      x))
Not so different from in wart, eh? Though I'd prefer to use an immutable version that doesn't use set! and boxes. Maybe something like this instead:

  (def copy -> (let x [ %copy f ]) @r
    (foldl (f x) (pair r) -> x [k v]
      (set x k v)))

-----

2 points by Pauan 4181 days ago | link

By the way, in case you're wondering... the %copy signature is this here:

  (f x)
Yeah, that tiny little thing. What that does is it takes the %copy method and calls it with the object as its first argument. That's it. As long as that doesn't change, then there's no need to update the objects.

So, if we wanted to add in some new copy functionality that required us to pass more information to the %copy method, we'd need to change the copy function to pass more arguments to the %copy method, which would require changes to all the objects that use the %copy method.

But in this case, we don't need to pass any more arguments to the %copy method so nothing changes except the copy function itself.

And the copy function uses @r which means "0 or more extra arguments", so it works just fine in the 1-argument case. Thus, this change is backwards compatible in every way (that I care about).

-----

1 point by akkartik 4181 days ago | link

> (def list args args)

Ah, thanks! I'd considered that but desisted out of a vague fear that the quoted-list gotcha might apply here: http://arclanguage.org/item?id=10248

-----

2 points by Pauan 4181 days ago | link

  > (def foo (s) (let a '(3) (= (car a) (cons s (car a)))))
  (3 . 3)
  (3 3 . 3)
  
  > (def foo (s) (let a (list 3) (= (car a) (cons s (car a)))))
  (3 . 3)
  (3 . 3)
  
  > (def foo (s) (let a ((fn args args) 3) (= (car a) (cons s (car a)))))
  (3 . 3)
  (3 . 3)
Yes, this change is fine.

-----