Arc Forumnew | comments | leaders | submit | d0m's commentslogin
1 point by d0m 5330 days ago | link | parent | on: ([_] 1) vs ((fn (x) x) 1)

Oh my ... thank you. That explains a lot of things :p

-----

1 point by d0m 5333 days ago | link | parent | on: Ask: PG and Anti-OO

Exactly, Arc already have OO with closures. However, what I suggest is this:

  (x 'deposit 400 "achtung")
could be instead written as:

  (deposit x 400 "achtung")
So basically, we get rid of the quote in front of deposit and we reverse the first and second parameter.

I find that:

  (let x (Bank-Account "meh")
    (deposit x 50)
    (check x))
Is cleaner then:

  (let x (Bank-Account "meh")
    (x 'deposit 50)
    (x 'check))

-----

3 points by waterhouse 5330 days ago | link

Given the way I defined a Bank-Account, you could implement this rather simply:

  (def deposit (x . args)
    (apply x 'deposit args))
Or, with akkartik's implementation, which I agree is nicer and better:

  (def deposit (x . args)
    (apply x!deposit args))
And you could make another "object" and give it a distinct 'deposit "method" and it would work fine.

So... it seems that one can easily implement these things in Arc. If you have some project in mind for which you want OO stuff, you can just do the things posted in this thread, and tell everyone how it goes if you like.

If you think OO should be put into the core of the language, well, there's a certain resistance to adding features just because someone suggests it. The core would become large, disorganized, and difficult to maintain if we did that. So, you would have to give a convincing argument as to why it's worth putting in there, and a demonstration of how useful it is in some real project of yours is probably the best argument you could give.

-----

3 points by akkartik 5331 days ago | link

Yes that's implementable; I find this even more expressive:

  (x!deposit 50 "meh")
The implementation is simpler than waterhouse's version - just return the obj:

  (def Bank-Account (password)
    (let money 0
      (let check-pass [unless (is _ password)
                        (err "Wrong password!")]
         (obj deposit (fn (x pw)
                        (check-pass pw)
                        (++ money x))
              withdraw (fn (x pw)
                         (check-pass pw)
                         (if (< money x)
                             (err "Not enough money.")
                             (-- money x)))
              check (fn (pw)
                      (check-pass pw)
                      money)
              change-pw (fn (new-pw pw)
                          (check-pass pw)
                          (= password new-pw))))))

-----

3 points by bogomipz 5317 days ago | link

This kind of OO with closures is a fun experiment and looks very elegant at first sight. I love the (x!deposit 50 "meh") version for its simplicity, the use of ssyntax, and the fact that you can pass x!deposit around as a first class function. Thanks to macros, you can of course easily come up with a nice syntax for the definitions:

  (defclass Bank-Account (password)
    (money 0 debt 0)
    (def check-pass (pw)
      (unless (is pw password)
        (err "Wrong password!")))
    (def deposit (x pw)
      (self!check-pass pw)
      (++ money x))
    (def withdraw (x pw)
      (self!check-pass pw)
      (if (< money x)
          (err "Not enough money.")
          (-- money x)))
    (def check (pw)
      (self!check-pass pw)
      money)
    (def change-pw (new-pw pw)
      (self!check-pass pw)
      (= password new-pw)))
However, the approach has some issues in real life use. First, every bank account instance replicates the method table and so takes up more memory the more methods the class defines, and each method is a closure that takes up memory as well. Also, this hash table obviously needs to be built every time an instance is created. Another big problem that follows from the above is that when you add or redefine methods on the class, existing instances are left with the old implementation. And there is no way to implement inheritance here.

I guess it is possible to remedy most or all of those problems by sacrifying methods as closures and instead do:

  (= bank-account-mt
    (obj check-pass (fn (self o pw)
                      (unless (is o!pw pw)
                        (err "Wrong password!")))
         deposit (fn (self o x pw)
                   (self 'check-pass pw)
                   (++ o!money x))
         withdraw (fn (self o x pw)
                    (self 'check-pass pw)
                    (if (< o!money x)
                        (err "Not enough money.")
                        (-- o!money x)))
         check (fn (self o pw)
                 (self 'check-pass pw)
                 o!money)
         change-pw (fn (self o new-pw pw)
                     (self 'check-pass pw)
                     (= o!pw new-pw))))

  (def Bank-Account (password)
    (let o (obj money 0 pw password)
      (afn (method-name . args)
        (apply (bank-account-mt method-name)
               (cons self (cons o args))))))
Again using a macro to improve readability and writability. Adding inheritance is left as an exercise for the reader.

-----

2 points by rocketnia 5317 days ago | link

I'm sure this doesn't surprise you, but here's a quick version of 'defclass that uses a syntax similar to your first example and an implementation similar to your second example:

  (mac defclass (name constructed-fields derived-fields . defs)
    (let mt (sym:string name '-mt)
      `(do (= ,mt (obj ,@(mappend
                           [do (case car._
                                 def  (let (name parms . body) cdr._
                                        `(,name (fn ,(cons 'self
                                                       (cons 'o parms))
                                                  ,@body)))
                                  (err:+ "An invalid 'defclass "
                                         "declaration was "
                                         "encountered."))]
                           defs)))
           (def ,name ,constructed-fields
             (let o (withs ,derived-fields
                      (obj ,@(mappend [list _ _]
                               (join constructed-fields
                                     (map car pair.derived-fields)))))
               (afn (method-name)
                 (fn args
                   (apply (,mt method-name)
                          (cons self (cons o args))))))))))
  
  (defclass Bank-Account (password)
    (money 0)
    (def check-pass (pw)
      (unless (is pw o!password)
        (err "Wrong password!")))
    (def deposit (x pw)
      self!check-pass.pw
      (++ money x))
    (def withdraw (x pw)
      self!check-pass.pw
      (when (< o!money x)
        (err "Not enough money."))
      (-- o!money x))
    (def check (pw)
      self!check-pass.pw
      o!money)
    (def change-pw (new-pw pw)
      self!check-pass.pw
      (= o!password new-pw)))

-----

1 point by bogomipz 5317 days ago | link

Nice, and you even changed it so x!deposit returns a function again! This does of course add some overhead since a closure is constructed every time you call a method, but still.

One thing I'm not quite happy with is that one has to write o!money. Would it somehow be possible to hide the o? Would it be possible to use !money or .money, or does the parser not allow that? And how to pass the hash table from the afn to the methods without polluting their namespaces? It could be done using a gensym, but then it is not possible to add methods to the method table outside defclass.

Perhaps doing something like this:

  (= bank-account-mt
    (obj check-pass (fn (self pw)
                      (unless (is self!ivars!pw pw)
                        (err "Wrong password!")))
         deposit (fn (self x pw)
                   self!check-pass.pw
                   (++ self!ivars!money x))
         withdraw (fn (self x pw)
                    self!check-pass.pw
                    (if (< self!ivars!money x)
                        (err "Not enough money.")
                        (-- self!ivars!money x)))
         check (fn (self pw)
                 self!check-pass.pw
                 self!ivars!money)
         change-pw (fn (self new-pw pw)
                     self!check-pass.pw
                     (= self!ivars!pw new-pw))))

  (def bank-account (password)
    (let ivars (obj money 0 pw password)
      (afn (selector)
        (if (is selector 'ivars)
            ivars
            (fn args
              (apply (bank-account-mt selector)
                     (cons self args)))))))
Then make defclass turn .foo into self!ivars!foo. Another macro could exist for (re)defining methods after the fact:

  (defmethod bank-account steal-money (x)
    (-- .money x))
Or even redefine Arc's def so you could do:

  (def bank-account!steal-money (x)
    (-- .money x))
since (bank-account 'steal-money) is not an atom and 'def could thus recognize it as different from an ordinary function definition.

-----

1 point by d0m 5336 days ago | link | parent | on: (map [string ".*" _] "test")

Interesting solutions :D

There's also:

  (string (intersperse ".*" (coerce "..." 'cons)))
edit:

  (string ".*" (intersperse ".*" (coerce "..." 'cons)))

-----

1 point by fallintothis 5336 days ago | link

Except intersperse does it in the wrong order:

  arc> (string (intersperse ".*" (coerce "test" 'cons)))
  "t.*e.*s.*t"
By the way, for code that won't screw up asterisks, just prefix the line by two spaces. :)

Edit: Ah. Well, I suppose appending an extra onto the front works. Still not optimal, but oh well.

-----

1 point by d0m 5334 days ago | link

Finally, I've chosen to make a little macro:

  (let s "hello"
    (->s s 
      (map [+ ".*" _] s)))

-----

1 point by akkartik 5334 days ago | link

Can you elaborate?

-----

2 points by d0m 5334 days ago | link

This macro hide fallintothis' suggestion to my string-are-not-list problem. So basically it does:

  (def ->s (var . body) 
   `(let ,var (coerce ,var 'cons)
      (string ,@body)))
And now I can use:

  (let s "hello"
    (->s s
      (intersperse ", " s)))
  
  > "h, e, l, l, o"

-----

2 points by fallintothis 5333 days ago | link

Yet another option:

  (def map-as (type f seq)
    (coerce (map1 f (coerce seq 'cons)) type))

  arc> (map-as 'cons [+ ".*" _] "test")
  (".*t" ".*e" ".*s" ".*t")
  arc> (map-as 'string [+ ".*" _] "test")
  ".*t.*e.*s.*t"
  arc> (map-as 'cons [+ _ 1] '(1 2 3))
  (2 3 4)
  arc> (map-as 'string [+ _ 1] '(1 2 3))
  "234"
This works because of how coerce works.

  arc> (coerce '("a" "b" "c") 'string)
  "abc"
It deals with lists and strings well, which is decent: Arc's only other sequence-like type is the table (I don't think you'd ever want to treat symbols as a sequence of 1-character symbols; you'd just use a string). Tables would work better if they were properly coerced, cf. the comment above tablist and listtab in arc.arc.

The more I think about it, the more I like this model. Conceptually, it seems that map should behave like

  (def map (f seq)
    (map-as (type seq) f seq))
even if it's not implemented like that -- all the coercions would surely be slow. (Tangential: map would also need to handle multiple sequences.) But it makes more sense for map and coerce to at least have compatible behavior. Plus, map's current behavior is a degenerate case of the coerce-compatible map:

  arc> (map inc "abc")
  "bcd"
  arc> (map-as 'string inc "abc")
  "bcd"
so it's not like you lose functionality, and then this post's example would've worked to begin with.

-----

1 point by akkartik 5331 days ago | link

When you phrase it as map-as, it becomes easier to fit into my defgeneric/defmethod framework (http://www.arclanguage.org/item?id=11865). Thanks!

I've spent some time thinking about how to extend it for multiple-dispatch, and I didn't want to also think about setting the arg index to dispatch on.

-----

1 point by d0m 5336 days ago | link | parent | on: (map [string ".*" _] "test")

Thank you, I get it.. And yeah, I think it could be cool if we could concat string instead of only characters in map.

Maybe a way to do that would be to coerce the string to a list (as you did), to use a normal map on this, and then, to join the new list with (string)

(apply string (map .. (coerce "test" 'cons)))

(And by the way, I didn't know about '(coerce string 'cons))

-----

2 points by aw 5336 days ago | link

Here's a mapstr which does this:

  (def mapstr (f . seqs)
    (string (accum a (for i 0 (- (apply min (map1 len seqs)) 1)
                       (a (string (apply f (map1 [_ i] seqs))))))))

  arc> (mapstr [string "." _] "test")
  ".t.e.s.t"
And we can plug that into the definition of map:

  (def map (f . seqs)
    (if (some [isa _ 'string] seqs) 
         (apply mapstr f seqs)
        ;; the rest is the same as the arc3.1 def
        (no (cdr seqs)) 
         (map1 f (car seqs))
        ((afn (seqs)
          (if (some no seqs)  
              nil
              (cons (apply f (map1 car seqs))
                    (self (map1 cdr seqs)))))
         seqs)))

  arc> (map [string "." _] "test")
  ".t.e.s.t"

-----

3 points by aw 5336 days ago | link

Though you know, it's funny, my brain doesn't actually like it that map returns different things depending in the types of its arguments. In the same way that a long run-on sentence is annoying if I can't tell what's it's saying until the end, if I see a map I don't know if it's returning a list or a string or whatever until I've looked at its arguments and seen what they are.

So I might like map to always return a list, but still have it treat a string as if it were a list of characters. The example then becomes:

  (string:map [string "." _]  "test")

-----

1 point by d0m 5336 days ago | link

Cool, I've learn a couple of things by reading this. (Mainly accum). Do you think modifying map this way could break things?

-----

1 point by aw 5336 days ago | link

Assuming that I didn't introduce some gratuitous bugs, it's safe because it works the same for programs which don't throw an error in Arc 3.1.

-----

2 points by d0m 5344 days ago | link | parent | on: More documentation

Really useful stuff for beginners like me. On a side node, I think examples would be useful in addition to the definition. (This is usually how I learn.. I watch the examples and I only read definition if I don't understand.)

Also, I think we really need a standard module system..

-----

1 point by d0m 5345 days ago | link | parent | on: Try Arc

Evanmurphy: Really interesting, thank you :)

-----

1 point by d0m 5358 days ago | link | parent | on: Macro question

Let's suppose however that I want to check if it is really in and not fruitloops.. Is there a way?

For example:

(foreach x in seq (prn x)) -> call (each x seq (prn x))

(foreach x seq (prn x)) -> call (each x seq (prn x))

(foreach x fruitloops seq (prn x)) -> (prn "mmmm")

-----

1 point by evanrmurphy 5358 days ago | link

It's not pretty, but you could do:

  (mac foreach (var . args)
    (if (is car.args 'in)
          `(each ,var ,(cadr args)
             ,@(cddr args))
         (is car.args 'fruitloops)
          `(each ,var ,(cadr args)
             (prn "yum"))
         (acons car.args)
           `(each ,var ,(car args)
              ,@(cdr args))))
Update: I'm not sure it's any more clear, but another option would be,

  (mac foreach (var . args)
    `(each ,var ,(if (in car.args 'in 'fruitloops)
                      cadr.args
                     car.args)
       ,@(case car.args
           'in         cddr.args
           'fruitloops `((prn "yum"))
                       cdr.args)))
except that it's not working correctly for the 'fruitloops case and I can't figure out why.

Upperdate: Aha! It's because in and fruitloops in the case statement should not be quoted:

  (mac foreach (var . args)
    `(each ,var ,(if (in car.args 'in 'fruitloops)
                      cadr.args
                     car.args)
       ,@(case car.args
           in         cddr.args                       ; in not quoted
           fruitloops `((prn "yum"))                  ; ditto fruitloops
                      cdr.args)))
This works.

-----

1 point by d0m 5358 days ago | link | parent | on: Macro question

In this case, it was only in so it reads more like english. But, I was interested in the more general idea about how to add keyword in macro.

-----

1 point by d0m 5358 days ago | link | parent | on: Macro question

Doesn't it make a little bit less pretty?

(foreach x in '(1 2 3)) vs (foreach x :in '(1 2 3)) ?

I'm sure you have good reasons to use this convention.. mind sharing them?

-----

2 points by evanrmurphy 5358 days ago | link

I think the colon is his convention for within the definition, as a visual marker to distinguish keyword parameters from others. When you later call a function or macro defined as such, you won't need to prepend the colon to arg you're passing (similar to how mine was called in within the definition but could be fruitloops when I called it).

Edit: On second thought, probably does use the colon when calling the function as well.

-----

3 points by shader 5357 days ago | link

The colon is a standby from other lisps; Common Lisp would intern symbols starting with : to a special KEYWORD symbol table.

That was important because you could do things like keyword based arguments to functions, etc. i.e. if you had a function that was defined as

  (defun greet (&key (a "Hello") (b "World")) 
    (print a " " b))
then you could call it like:

  >(greet)
  Hello World
  >(greet :b "Bob")
  Hello Bob
  >(greet :b "Alice" :a "Yo")
  Yo Alice
etc. As you can see, it's a lot more flexible than "traditional" optional arguments, since you can specify any of them, and in any order.

-----

1 point by akkartik 5357 days ago | link

Yes I've been using the : in calls as well, but as I wrote the comment it occurred to me that I didn't have to. Either works.

-----

2 points by akkartik 5357 days ago | link

The prettiness wasn't a concern; as programmers we're used to needing hyphens or underscores, to ignoring parentheses and focusing on indentation. The colon's like that.

-----

2 points by d0m 5358 days ago | link | parent | on: Macro question

Oh wow, thank you. For some reasons, I thought I couldn't just write (var in expr. body) since in the call:

(foreach x in '(..)), "in" would have been evaluated to an unknown symbol.. However, I was totally wrong since I don't even have to evaluate it! :)

-----

More