Arc Forumnew | comments | leaders | submit | ylando's commentslogin
1 point by ylando 5554 days ago | link | parent | on: Ideas about arc object system.

In my own opinion:

Shallow object systems have an advantage of better libraries design; But Arc need a real object system.

Arc need all the basic features for some one to take it seriously.

-----

2 points by rocketnia 5553 days ago | link

What do you like about having an object system, specifically? I wouldn't mind having one too, but I think object systems get complicated and arbitrary very quickly. I'd rather not have one monolithic abstraction where several highly-focused ones would do.

I do find myself switching to Groovy a lot thanks to its object system. I think what I mainly like is the ability to make an instance of FooSubclass and automatically have "foo in FooSuperclass" and "foo in FooInterface" be true.

Using Arc's built-in notion that the type of an object is the result of calling the 'type function on it (which is configurable via 'annotate), it's easy enough to achieve a form of inheritance that accomplishes what I just described:

  ; This is a table from type symbols to lists of their supertypes.
  ; Indirect supertypes are included in these lists, but no type is a
  ; member of its own list. There are no inheritance loops, either;
  ; inheritance here is a directed acyclic graph, and this is the
  ; transitive closure of that graph.
  (= indirect-inheritance* (table))
  
  (def inherits (subtype supertype)
    (~~mem supertype (cons subtype indirect-inheritance*.subtype)))
  
  (def fn-def-inherits (subtype . supertypes)
    (each supertype supertypes
      (when (~inherits subtype supertype)
        (when (inherits supertype subtype)
          (err "There was an inheritance loop."))
        (let supers (cons supertype indirect-inheritance*.supertype)
          (zap [union is supers _] indirect-inheritance*.subtype)
          (each (k v) indirect-inheritance*
            (when (mem subtype v)
              (zap [union is supers _] indirect-inheritance*.k)))))))
  
  (mac def-inherits (subtype . supertypes)
    `(fn-def-inherits ',subtype ,@(map [do `',_] supertypes)))
  
  (def isinstance (x test-type)
    (inherits type.x test-type))
  
  (def a- (test-type)
    [isinstance _ test-type])
Come to think of it, I can't think of anything else object-oriented I miss in Arc. (OO is good for dispatch too, but I've already made an Arc dispatch library I like better.) I'll make sure to try this out the next time I program something substantial in Arc.

So, I've talked a lot about me, but my question to you still stands. What features are you looking for in an object system?

-----

3 points by ylando 5553 days ago | link

In my own opinion:

The most important feature of object oriented is name compressing. Imagine a program with a lot of different databases. Now you have to give names for functions that add element, You will have to give them names like:

  add-worker, add-statistic, add-html-text ...
Now you have to remember all those names.

-----

3 points by rocketnia 5552 days ago | link

That sounds like dispatch to me. ^_^ You'd like to use one name but give it multiple meanings based on the types involved. This thread (http://arclanguage.org/item?id=11779) has a couple of approaches to dispatch from akkartik and myself.

Hmm, I've been having trouble for a while, trying to figure out my favorite way to have inheritance and multiple dispatch at the same time. (Single dispatch with inheritance seems more straightforward, but I'd rather not bother with it if I could use multiple dispatch instead.) If one method signature is (A, B) -> C, and another is (B, A) -> C, and A is a subtype of B, then what happens if you call the method with (a, a)? That could just cause an error, or the first argument could take precedence, but I'm not sure which approach I like better.

-----

1 point by ylando 5552 days ago | link

I think that multi dispatch is good idea for strong type languages. If c++ encounters this problem it will solve it at compile time (I think it will try to find the solution that is best for the first variable then the second and so on). For a dynamic language like arc multi dispatch will have an efficiency cost. In my own opinion:

It is one of the "too smart to be useful" features of common lisp. It is one of the reason people hate clos.

I have a question for you: In what way a multi dispath is better then the alternatives? You can dispatch only on the first argument or hold a virtual table of functions for every object.

-----

1 point by rocketnia 5552 days ago | link

For a dynamic language like arc multi dispatch will have an efficiency cost.

I don't think the efficiency cost for dynamic dispatch on the first argument would be significantly different, and I'm not worried about either one. I think they're at worst as inefficient as a sequence of if statements, which is to say they would take constant time (given a constant number of methods to check and constant time for each check).

Constant time can still be painful in the right quantity, and making the decision at compile time would indeed help, but Arc doesn't have the necessary static type information to go on (as you know). So multiple dispatch and single dispatch alike have to suffer a bit.

It is one of the reasons people hate clos.

Well, I've heard nothing but good things about CLOS, but then I haven't heard that much. ^_^ Is there some good anti-CLOS reading material you can recommend? XD

In what way is multi dispatch better then the alternatives?

It's partly a matter of name compression. ^_-

  ; no dispatch
  (collide-spaceship-with-spaceship s s)
  (collide-spaceship-with-asteroid s a)
  (collide-asteroid-with-spaceship a s)
  (collide-asteroid-with-asteroid a a)
  
  ; single dispatch (on the first argument)
  (collide-with-spaceship s s)
  (collide-with-asteroid s a)
  (collide-with-spaceship a s)
  (collide-with-asteroid a a)
  
  ; double dispatch
  (collide a s)
  (collide s a)
  (collide a s)
  (collide a a)
Yeah, I stole Wikipedia's example here. Another example is a comparison function that needs to compare values of many different types. Yet another is a "write" function which has different behavior for each type of writer object and each type of object to be written.

When it comes right down to it, I just believe multiple dispatch ought to be a simpler concept than single dispatch, since it imposes fewer restrictions on the programmer. Unfortunately, it seems to replace one arbitrary restriction with a bunch of (what I see as) arbitrary design decisions. ^_^;

-----

0 points by ylando 5551 days ago | link

For every one of your examples, we can use double dispatch design pattern; For example:

  (collide o1 o2) call 
  (collide-with-spaceship o2 o1) if o1 is a spaceship
  or (collide-with-asteroid o2 o1) if o1 is an asteroid.
I think that you can even abstract this design pattern with a macro (but I did not try it). So you still did not convince me that multi dispatch is a good feature.

-----

1 point by evanrmurphy 5554 days ago | link

> Arc need all the basic features for some one to take it seriously.

As long as implementing any feature for the now doesn't compromise Arc's quality in the long term, I doubt anyone here would disagree with you. Keep in mind though that Arc's core language is still subject to change (http://www.paulgraham.com/core.html).

-----

2 points by ylando 5555 days ago | link | parent | on: Problems with arc

It can look like this:

  In a library file mylib.arc
  (namespace mylib)
  (def func-a ...)
  ...
In a program:

   (use mylib (func-a func-b)) or (use mylib *) or (use mylib)
   Now we can write
   (func-a ...) (func-b ...)
   and for a function that we didn't import inside the
   namespace; we can write:
   (mylib/func-c ...)

-----

3 points by evanrmurphy 5554 days ago | link

It might be more elegant to take advantage of s-expressions for this, as in

  (module mylib
    (def func-a ...))
and

  (w/module mylib
    (func-a ...))
rather than having imperative declarations that apply to an entire file.

Thinking about module/namespace systems in general, I guess as long as definitions are still global by default then they shouldn't bother anyone who doesn't want them. You might also be interested in aw's piece on library dependencies: http://awwx.ws/thinking-about-dependencies

-----

3 points by ylando 5556 days ago | link | parent | on: Problems with arc

   I will explain:
   1) I think we should separate the definition and the
   assignment some thing like:
   use strict; 
   my x; # declare var for the first time
   x=3;  # assign a value to a var
   in perl.
   3) Suppose we want to make an object oriented
   function; we can write a function
   (def myfunc (this arg1 arg2) ...)
   If we had alias we could write a macro that expand to
   (w/alias (var this.var var2 this.var2)
   (def myfunc (this arg1 arg2) ...))

-----

3 points by rocketnia 5556 days ago | link

1) I think you're missing the whole point of 'let.

  arc> (= x 5)
  5
  arc>
    (let x 3         ; "declare" a var with value 3
      (= x (* x x))
      (+ x 2))
  11
  arc> x
  5
If you want to separate the declaration from the initial value... why? What happens if you use the variable in between those two times?

2) For what it's worth, my Lathe library provides a certain sort of namespaces, and I've been using those pretty happily. (http://arclanguage.org/item?id=11610) But of course I'd be happy with them, 'cause I'm their author. :-p

3) Why would you need a macro to expand to...

  (w/alias (var this.var var2 this.var2)
    (def myfunc (this arg1 arg2)
      ...))
...when it could already expand to something like this?

  (def myfunc (this arg1 arg2)
    (with (var this!var var2 this!var2)
      ...))
That said, I think 'symbol-macro-let would be nifty. I wonder if it could be even more useful to have some way to build a lambda whose free variables were treated in a custom way (as opposed to just being globals). Either one of these could be a basis for scoped importing of namespaces.

-----

4 points by zck 5556 days ago | link

So w/alias is like let, but any changes are shadowed back to where the alias came from?

So

  (let var (cons 1 2)
    (w/alias head car.var
      (= head 2))
    var)
would output

  (2 . 2)
This seems related to macros and backquoting, although I can't quite explain it.

-----

4 points by ylando 5558 days ago | link | parent | on: Adding local scope to arc

Thanks to the remark of rocketnia. I have found some bugs in the code. Here is the code after I remove the bugs:

   (def span-let (lst)
   (if (empty lst) ()
       (mem (caar lst) binding-function-list*) 
          (list (join (car lst) (span-let (cdr lst))))   
       (and (is (type (caar lst)) 'cons) (caris (caar lst) 'scope)) ;scope
          (if (empty (cdr (car lst))) 
              (cons (caar lst) (span-let (cdr lst)))
              (cons (caar lst) (span-let (cons (cdr (car lst)) (cdr lst))))) ;forget @ after scope 
      (cons (car lst) (span-let (cdr lst)))))

  (def shuffle-numbers (min max)
  ;Get a minimum and maximum numbers
  ;Return a shuffle array of numbers from the minimum number to the maximum
  (scope
     w/table arr @
     for i 0 (- max min) 
        (= arr.i (+ i min)) @ ; init arr
     down i (- max min) 1
        (swap arr.i (arr (rand (+ 1 i)))) @  ; shuffle array elements
  ))  
Scope get a list of function calls and macro calls; so I add a macro for returning a value:

  (mac id (x) `,x)

-----

1 point by ylando 5558 days ago | link | parent | on: Adding local scope to arc

Thanks for your nice responce. It was very helpfull. Append works in jarc but i didn't find any document on this function.

-----

2 points by ylando 5561 days ago | link | parent | on: Wish list.

Here is my idea of scope implementation:

   (def split@ (lst (o acc)) 
      (if (empty lst) (list (rev acc)) 
         (caris lst '@) (cons (rev acc) (split@ (cdr lst)))
         (split@ (cdr lst) (cons (car lst) acc))))

  (def span_let (lst)
     (if (empty lst) ()
       (is (caar lst) 'let) 
             (list (append (car lst)
                           (span_let (cdr lst))))   
             (cons (car lst) (span_let (cdr lst))) ))

   (mac scope lst (cons 'do (span_let (split@ lst))))
But it will have a way of holding a list of binding functions and macros like let to make it more flexible. It will support scope inside scope.

-----

1 point by ylando 5563 days ago | link | parent | on: Wish list.

Thank you for your answers.

When I talk about scope i meant a region where you can separate the function calls by a character, for example @ and the let statement span through all the region. You can easily write a macro that do (scope let x 2 @ let y 4 @ print (* x y) ) But it seem useful to me, why not have it in the core?

A class macro (this is how i call it) can be use to implement a more general case of ruby blocks in lisp. May be this idea has a problem of making a bloated binary code.

-----

1 point by zck 5562 days ago | link

Is

  (scope let x 2 @ let y 4 @ print (* x y) )
analogous to Common Lisp's let, or is it let* ?

I'm a little unclear as to what @ really does. What is the reason you want @? Is it just for let statements, or for any statement? Is it to make cleaner code, or for efficiency? Can you post some code that you think works better or is more elegantly written using @, rather than without it? Maybe some code of 5-10 lines?

-----

2 points by fallintothis 5562 days ago | link

I figure it's something like

  (mac scope exprs
    (trav exprs [if (caris _ '@)
                    (list (self (cdr _)))
                    (cons (car _) (self (cdr _)))]))
So then

  arc> (macex1 '(scope let x 2 @ let y 4 @ prn (* x y)))
  (let x 2 (let y 4 (prn (* x y))))
I think the idea is to reduce parentheses, but historically these sort of tricks don't seem to pan out.

-----

1 point by rocketnia 5562 days ago | link

I've actually wanted something similar from time to time, but without the '@ and 'let. Right now, I have just one place I really miss it (which you can see at http://github.com/rocketnia/lathe/blob/c343b0/arc/utils.arc), and that place currently looks like this:

  (=fn my.deglobalize-var (var)
    (zap expand var)
    (if anormalsym.var
      var
      
      ; else recognize anything of the form (global 'the-var)
      (withs (require     [unless _
                            (err:+ "An unrecognized kind of name was "
                                   "passed to 'deglobalize-var.")]
              nil         (do.require (caris var 'global))
              cdr-var     cdr.var
              nil         (do.require single.cdr-var)
              cadr-var    car.cdr-var
              nil         (do.require (caris cadr-var 'quote))
              cdadr-var   cdr.cadr-var
              nil         (do.require single.cdadr-var)
              cadadr-var  car.cdadr-var
              nil         (do.require anormalsym.cadadr-var))
        cadadr-var)
      ))
(Note that the Lathe library defines 'anormalsym to mean [and _ (isa _ 'sym) (~ssyntax _)]. Also, 'my is a Lathe namespace here, and I use Lathe's straightforward '=fn and '=mc to define things in namespaces. Finally, don't forget that (withs (nil 2) ...) binds no variables at all ('cause it destructures).)

If I define something like 'scope, I save myself a few parentheses and nils:

  (=mc my.scope body
    (withs (rev-bindings nil
            final nil
            acc [push _ rev-bindings])
      (while body
        (let var pop.body
          (if no.body
            (= final var)
              anormalsym.var
            (do do.acc.var (do.acc pop.body))
            (do do.acc.nil do.acc.var))))
      `(withs ,rev.rev-bindings ,final)))
  
  (=fn my.deglobalize-var (var)
    (zap expand var)
    (if anormalsym.var
      var
      
      ; else recognize anything of the form (global 'the-var)
      (my:scope
        require     [unless _
                      (err:+ "An unrecognized kind of name was passed "
                             "to 'deglobalize-var.")]
                    (do.require (caris var 'global))
        cdr-var     cdr.var
                    (do.require single.cdr-var)
        cadr-var    car.cdr-var
                    (do.require (caris cadr-var 'quote))
        cdadr-var   cdr.cadr-var
                    (do.require single.cdadr-var)
        cadadr-var  car.cdadr-var
                    (do.require anormalsym.cadadr-var)
                    cadadr-var)
      ))
Furthermore, I suspect this version of 'scope would almost always be preferable to 'do, 'let, and 'withs, partly because it's easier to refactor between those various cases. However, it doesn't do any destructuring, and it's probably a lot harder to pretty-print. (I'm not even sure how I would want to indent it in the long term.)

-----

2 points by fallintothis 5562 days ago | link

That example doesn't really call out to me: it looks like you could save yourself a few parentheses and nils by refactoring with something simpler, rather than with a complex macro. E.g., if I understand the code correctly:

  (=fn my.aglobal (var)
    (and (caris var 'global)
         (single:cdr var)
         (caris var.1 'quote)
         (single:cdr var.1)
         (anormalsym var.1.1)))

  (=fn my.deglobalize-var (var)
    (zap expand var)
    (if (anormalsym var)
         var
        (aglobal var)
         var.1.1
        (err "An unrecognized kind of name was passed to 'deglobalize-var.")))
But then, I avoid setting variables in sequence like that.

-----

2 points by rocketnia 5561 days ago | link

Hmm, I kinda prefer local variables over common subexpressions. It's apparently not for refactoring's sake, since I just name the variables after the way they're calculated, so it must just be a premature optimization thing. :-p

But yeah, that particular example has a few ways it can be improved. Here's what I'm thinking:

  (=fn my.deglobalize-var (var)
    (zap expand var)
    (or (when anormalsym.var var)
        (errsafe:let (global (quot inner-var . qs) . gs) var
          (and (is global 'global)
               (is quot 'quote)
               no.qs
               no.gs
               anormalsym.inner-var
               inner-var))
        (err:+ "An unrecognized kind of name was passed to "
               "'deglobalize-var.")))
I still like 'scope, but I'm fresh out of significant uses for it.

-----

2 points by fallintothis 5561 days ago | link

(To continue the digression into this particular case...) I had thought about destructuring, but found the need for qs and gs ugly. Really, a pattern matching library would be opportune. But since there's no such luck in Arc proper, it'd be ad-hoc and probably altogether not worth it (though not difficult to implement). I say this with respect to vanilla Arc; I don't know if Anarki has such a library. Still, it'd be hard to beat something like

  (=fn my.deglobalize-var (var)
    (zap expand var)
    (or (check var anormalsym)
        (when-match (global (quote ?inner-var)) var
          (check ?inner-var anormalsym))
        (err "An unrecognized kind of name was passed to 'deglobalize-var.")))

-----

2 points by akkartik 5562 days ago | link

I'm not sure I fully understand the features you're suggesting, but the question should be "does it have to be in the core". Err on the side of keeping the core as minimal as possible unless there is a clear reason to do otherwise.

-----

1 point by rocketnia 5562 days ago | link

Exactly. The core can't contain everything everybody would ever find useful. Instead, it's the core's job (IMO) to make it easy to define those things as they come up.

Additionally, it may or may not be the core's job to encourage canonical APIs for non-fundamental but really useful stuff, just so that programmers don't end up defining the same things over and over in ways that are flawed or incompatible with each other. I think this "may or may not" issue is the R7RS split in a nutshell.

In my own opinion, a "core" that tries to provide a comprehensive bookshelf of useful but programmer-definable things isn't a language; it's a utility library. Some libraries may be extremely popular, and they may even be standardized, distributed, and/or documented alongside their languages, but it's ultimately up to the development team to choose the combination of languages and libraries that's right for the project.

-----