Contexts as Objects

Notices and updates
kinghajj
Posts: 30
Joined: Sun Jul 15, 2007 2:37 pm

Contexts as Objects

Post by kinghajj »

I wrote a post at http://kinghajj.blogspot.com/2007/09/co ... wlisp.html detailing how to use contexts to implement classes and objects in newLISP. I've just discovered this--though I'm probably not the first--so I haven't found out all that you can do with this.

Hope you enjoy it.

cormullion
Posts: 2038
Joined: Tue Nov 29, 2005 8:28 pm
Location: latiitude 50N longitude 3W
Contact:

Post by cormullion »

i did! thanks...

kinghajj
Posts: 30
Joined: Sun Jul 15, 2007 2:37 pm

Post by kinghajj »

I thought that you might have found this already. Is there a link to where you describe it?

cormullion
Posts: 2038
Joined: Tue Nov 29, 2005 8:28 pm
Location: latiitude 50N longitude 3W
Contact:

Post by cormullion »

I meant - I did enjoy your post. :-)

I know very little about object stuff. There's been some sporadic discussion on the board in the past - eg

http://www.alh.net/newlisp/phpbb/viewtopic.php?t=1058

But I'm still at the basic stages and haven't made much use of it.

Jeff
Posts: 604
Joined: Sat Apr 07, 2007 2:23 pm
Location: Ohio
Contact:

Post by Jeff »

Keep in mind that all contexts, including context prototypes, are children of MAIN. You therefore cannot create objects that store objects (at least using contexts). But lisps work better with functional programming than OOP.
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

Dmi
Posts: 408
Joined: Sat Jun 04, 2005 4:16 pm
Location: Russia
Contact:

Post by Dmi »

But lisps work better with functional programming than OOP.
Hmm... Is there some article about functiolal vs OOP design?
Many of us coming to newLISP from procedural/OOP world, we all need in examples about successful functional technics, that can replace OOP, I think.
WBR, Dmi

Jeff
Posts: 604
Joined: Sat Apr 07, 2007 2:23 pm
Location: Ohio
Contact:

Post by Jeff »

I'm sure there probably is, but probably not specifically related to newLISP. I'm sure I could write one this week, though. I'll post it when it's done.
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

Dmi
Posts: 408
Joined: Sat Jun 04, 2005 4:16 pm
Location: Russia
Contact:

Post by Dmi »

It would be cool, Jeff!
WBR, Dmi

Jeff
Posts: 604
Joined: Sat Apr 07, 2007 2:23 pm
Location: Ohio
Contact:

Post by Jeff »

Jeff
=====
Old programmers don't die. They just parse on...

Artful code

Dmi
Posts: 408
Joined: Sat Jun 04, 2005 4:16 pm
Location: Russia
Contact:

Post by Dmi »

http://artfulcode.nfshost.com/ - Site Temporarily Unavailable
WBR, Dmi

Jeff
Posts: 604
Joined: Sat Apr 07, 2007 2:23 pm
Location: Ohio
Contact:

Post by Jeff »

Yeah, I know. They are moving the servers to a new (physical) location. They did it at the last minute. They will be back up soon, hopefully.
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

Dmi
Posts: 408
Joined: Sat Jun 04, 2005 4:16 pm
Location: Russia
Contact:

Post by Dmi »

Very nice, Jeff!
I like your examples and explanation tricks!

But there is nothing versus OOP.
Personally, me thinking that good design plus lispish tricks are sufficient to not to use OOP in 95% cases. But this requires different code planning strategy.

I looking for a document which can explain (with examples) how to successfully build _really_ big programs using down-to-up programming, language-building-language etc. tricks - all this words looks beautiful, till U start to construct something bigger than 1000 lines of code yourself :-\

Unfortunately, english isn't my native, so I can't briefly overlook all-the-books-around - and I looking for some recommendations.

I want to look at "OOP design vs functional design" explanation like U explain "procedural coding vs functional coding" styles.
WBR, Dmi

Jeff
Posts: 604
Joined: Sat Apr 07, 2007 2:23 pm
Location: Ohio
Contact:

Post by Jeff »

It is more intuitive to compare functional code to imperative code, rather than to OO or procedural or whatever. OOP is a technique of storing namespaces along with functions to manipulate their data.

Any comparison I could come up with would give you the wrong idea. You want to just forget OOP for a moment and stop thinking about programs as a series of commands. Think in terms of list manipulation. Think about (map).

In OOP, you have an object that manipulates itself or performs actions on its own behalf.

With functional programming, you might have a list or items. Rather than each of those items performing actions on their own behalf, you would map the manipulator function to each of those items, creating a new list with the new values.

Here is a quick comparison of OO vs functional. I want to change a list of characters to uppercase.

OOP (using Python for simplicity):

Code: Select all

class Character: 
  def __init__(self, char): 
    self.char = char 
  def capitalize(self): 
    return self.char.capitalize()

class CharacterGroup:
    def __init__(self, *args):
        self.chars = []
        for c in args:
            self.chars.append(Character(c))
    def capitalize(self):
        for c in self.chars:
            yield c.capitalize()

group = CharacterGroup("a", "b", "c", "d")
for c in group.capitalize():
    print c
The functional solution would be something like:

Code: Select all

(let ((chars '("a" "b" "c" "d")))
  (map println (map upper-case chars)))
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

Dmi
Posts: 408
Joined: Sat Jun 04, 2005 4:16 pm
Location: Russia
Contact:

Post by Dmi »

OOP is not for coding. It's mainly for projecting.

(map) etc. are low level functional coding techniques. Their analogs is imperative/procedural programming, not OOP.

OOP is firstly a proper object decomposition, that itself is really declarative. Imperativness is only in method implementations and it isn't so interesting.

OOP offers to a programmer a mechanism of assembling and interoperation of pieces of code, that implements a different behavior (OOP GUI design is very effective)

OOP offers a way for interface specification (like JavaBeans are).

OOP isn't need for upper-casing etc, I think :-) Many fully procedural languages have many nice tricks for that.

But how about all the OOP benefits in a functional lispish style? Yes, we must forget OOP for that, but what will be in place of it?
WBR, Dmi

Jeff
Posts: 604
Joined: Sat Apr 07, 2007 2:23 pm
Location: Ohio
Contact:

Post by Jeff »

Well, upper-casing characters is obviously a contrived example, but it shows the difference well. OO is a way of defining complex types. The need for overly complex types is a fundamental lack of good types in a language. Although I do wish that newLISP had defstruct :)

Common Lisp has CLOS (common lisp object system) and Scheme can sort of fudge OOP, but most lisps kind of look down on OO. newLISP can do prototyping using contexts (which is super handy for many things, but not so great for OOP).
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

m i c h a e l
Posts: 394
Joined: Wed Apr 26, 2006 3:37 am
Location: Oregon, USA
Contact:

Post by m i c h a e l »

This is a topic of serious interest to me. I've been mulling over the ways in which the concepts of OOD/OOP can be mapped onto the functional paradigm. No aha moment yet. Considering the number of programmers who have absorbed the OO mentality, a helpful guide from 'there' to 'here' would be of considerable worth.

What I miss most from OOD are the high-level diagrams of objects and their relationships. The struggle for an individual programmer is with complexity (the problem OO was supposed to resolve). Whereas abstraction is the usual way to handle complexity in OO, I find newLISP -- a realist -- laughs at all that unnecessary structure. The more structure you add on, the more you have to learn and remember in order to use it properly. For an individual programmer, it makes sense to use a language that encourages straightforward and simple programming. But when those files begin to fill up with functions or I'd like to start with a high-level design, I start wishing for a diagrammatic way of representing all that complexity.

Having spent over 15 years in OOL (Object-Oriented Land), I'm finding my stay in the Land of Functions mildly perplexing. Maybe the answer is in the Tao-like mantra, "Be the object." Now, if I can just learn to be the function ;-)

m i c h a e l

Dmi
Posts: 408
Joined: Sat Jun 04, 2005 4:16 pm
Location: Russia
Contact:

Post by Dmi »

:-))

Yes. But in the other hand:

OO is only a way to develop the complex applications. All other applications of OO are not means.

But really, OO isn't mandatory. If you can do a good design, simply procedural or functional ways will fit yor needs. Unfortunately, most procedural languages have restrictions (like laks of _good_ lambdas, macro-functions, untyped symbols etc.). I belive that OO was introduced over procedural languages only to compensate that restrictions - otherwise it has never can got popularity.

Functional languages initially have no restrictions of procedural languages, so, I belive, the good developing is able completely without OO. In common, functional methods are mostly "natural-language" - oriented. And this really works fine - my newlisp code has unbeliveable not many mistakes against my similar code on procedural languages.

But I looking for a complete "functional developing style" guide somewhere...
...like there are many explanations over the internet and book stores about how OO must be properly used.
WBR, Dmi

rickyboy
Posts: 607
Joined: Fri Apr 08, 2005 7:13 pm
Location: Front Royal, Virginia

Re: Contexts as Objects

Post by rickyboy »

kinghajj wrote:I wrote a post at http://kinghajj.blogspot.com/2007/09/co ... wlisp.html detailing how to use contexts to implement classes and objects in newLISP.
Nice entry -- short and sweet. BTW, there is an error (really, a typo causing an error) in your code:

Code: Select all

(context 'Point)
(define (Point:Point _x _y)
  (setq x _x)
  (setq y _y))

(define (distance-from p)
  (sqrt
    (add
      (pow (sub p:x y) 2)
      (pow (sub p:y y) 2))))
(context 'MAIN)
Do you see it? :-)
(λx. x x) (λx. x x)

Jeff
Posts: 604
Joined: Sat Apr 07, 2007 2:23 pm
Location: Ohio
Contact:

Post by Jeff »

http://www.cs.cmu.edu/~dst/LispBook/

That is where I learned the fundamentals of lisp. Lisp isn't strictly functional. Lisp had map before the concept of "functional" programming was invented. In fact, when the book was written (sometime in the 80s, although it is entirely still applicable), the technique was called applicative programming. The other main lisp programming style was recursive.

Lisp has other means of abstraction. OO abstracts the noun and then uses procedural techniques to manipulate that abstraction.

When I write a large lisp program, I typically think about how I want to use it. I write a sample of how I intend to use the code. Then I begin to make each part of the sample work. I will often try to abstract out every single contiguous procedure into its own function.

This results in a large number of functions, but (for me) this is a direct result of the ease of s-expressions. By making *all* operators prefix operators, there is no syntactic difference between them. This means that if I use a function in a specific way or for a specific purpose, I can make the rest of my code incredibly easy to read and use by abstracting that single command into its own named function. When I do this enough, the resulting code that uses this becomes much easier to read, and when you update something, you typically only need to update things in one or two places (in the specific functions handling a particular structure, for example), rather than in the entire program.
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

kinghajj
Posts: 30
Joined: Sun Jul 15, 2007 2:37 pm

Re: Contexts as Objects

Post by kinghajj »

rickyboy wrote:
kinghajj wrote:I wrote a post at http://kinghajj.blogspot.com/2007/09/co ... wlisp.html detailing how to use contexts to implement classes and objects in newLISP.
Nice entry -- short and sweet. BTW, there is an error (really, a typo causing an error) in your code:

Code: Select all

(context 'Point)
(define (Point:Point _x _y)
  (setq x _x)
  (setq y _y))

(define (distance-from p)
  (sqrt
    (add
      (pow (sub p:x y) 2)
      (pow (sub p:y y) 2))))
(context 'MAIN)
Do you see it? :-)
Sorry, I don't see the typo :( What is it?

cormullion
Posts: 2038
Joined: Tue Nov 29, 2005 8:28 pm
Location: latiitude 50N longitude 3W
Contact:

Post by cormullion »

(sub p:x y): your code only gives the right answer when x = y. Unluckily, your example happened to give the correct answer since x = y = 1.

Dmi
Posts: 408
Joined: Sat Jun 04, 2005 4:16 pm
Location: Russia
Contact:

Post by Dmi »

Very nice! Thanks Jeff.
/me gone read :-)
WBR, Dmi

cormullion
Posts: 2038
Joined: Tue Nov 29, 2005 8:28 pm
Location: latiitude 50N longitude 3W
Contact:

Post by cormullion »

I've been working on kinghajj's object ideas a bit, so that i can add a bit about object-oriented programming to my introduction to newlisp. I don't know anything about the subject, so I've got a few questions... I know that newLISP isn't designed for object-based programming, but it seems that people want to know more...

1: The idea of using points seems a good example. But if you create hundreds of point objects and have a reasonable number of methods in the object/class, aren't you duplicating the code every time you create a new object? Or is newlisp smart enough to not duplicate the methods...?

2: The basic way of creating points:

Code: Select all

(new Point 'point1)
(point1 1 1)
is then made easier with the new-object macro:

Code: Select all

(new-object Point point1 1 1)
If I want to create a list of point objects from code, I can do it like this for the first way:

Code: Select all

 (dotimes (n 20)
      (set 'p (new Point (context (sym (string {pt} n)))))
      (p (rand 100) (rand 100))
      (push p MAIN:points-list -1))
 
but how can I do it with the second, macro way?

3: context changing

In the above loop, the context keeps changing - it starts at MAIN but finishes in pt19, hence the use of MAIN. Is this the only way to use 'new'?

To sort the points list into distance from origin order, I'm applying the distance-from to the context, then storing the distance in the points list. This is fine, but it seems to be not very object-oriented any more. Is there a better way?

Code: Select all

  (new-object Point origin 0 0)
  (dolist (c points-list)
    (set 'dist (apply (sym 'distance-from c) (list origin)))
    (nth-set $idx points-list (list c dist)))
  (sort points-list (fn (x y) (< (last x) (last y))))

Lutz
Posts: 5289
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

Code: Select all

aren't you duplicating the code every time you create a new object
Yes, every new copy of the template will also copy the functions (methods in OO lingo). This is why contexts as objects make only sense when the data content of the object is big compared to it's method overhead.

In one of his posts Kinghajj suggested a 'def-struct' macro. Inspired by this here is a 'def-class' macro which uses the context packaging mechanism only for the object maker, initialization, data setter and getter methods, but leaves the object data itself in a simple Lisp list.

This then creates a functional way of doing OO programming:

Code: Select all

(define-macro (def-class)
  (let (ctx (context (args 0 0)))
    (set (default ctx) (lambda () (args)))
    (dolist (item (rest (args 0)))
      (set (sym item ctx)
      (expand '(lambda-macro (_str _val) 
          (if (set '_val (eval _val)) 
          (nth-set ((eval _str) $idx) _val)    
          ((eval _str) $idx))) '$idx)))
ctx))
The expression '(default ctx)' is the same as '(sym (name ctx) ctx)' but much shorter and faster. The macro creates the default functor of the class/context as the object constructor and also creates the setter and getter functions for the object data members. Now we can create create functional definitions of objects:

Code: Select all

(def-class (point x y))

(set 'point1 (point 1 2)) => '(1 2) ; constructor with init values

(point:x point1) => 1  ; get x
(point:y point1) => 2  ; get y

(point:y point1 5) ; set y to 5

point1 => (1 5)
It is easy to add other functions to the point class:

Code: Select all

(define (point:distance p1 p2)
  (sqrt (add (pow (sub (p2 0) (p1 0)) 2) 
             (pow (sub (p2 1) (p1 1)) 2)))
)

(set 'point1 (point 1 1))
(set 'point2 (point 2 2))

(point:distance point1 point2) => 1.414213562
Objects defined this way are easily nested:

Code: Select all

(def-class (line p1 p2))
(define (line:length l)
  (apply point:distance l))

(set 'myline (line (point 1 2) (point 3 4))) => ((1 2) (3 4))

(line:length myline) => 2.828427125
Note how the point definition is nested in the line definition when constructing a new line object. When defining
the 'line:length' function the 'point:difference function can be used to define it.

Perhaps this should be the new way to promote OO programming in newLISP?

Lutz
ps: note that this is not true classic OO where the object "knows" of the class it belongs to. In the above example methods and data reside in separate memory objects. But from a code reading/writing point of view it is very OO like. Using 'new' and 'define-new' on classes created by 'def-class' even inheritance and sub-classing could be simulated.
Last edited by Lutz on Fri Oct 05, 2007 3:59 pm, edited 3 times in total.

Jeff
Posts: 604
Joined: Sat Apr 07, 2007 2:23 pm
Location: Ohio
Contact:

Post by Jeff »

I think this is misleading. OO is a paradigm that newLISP does not follow. Prototyping objects is not the same as object oriented programming (and I am not arguing *for* OO in newLISP).

The reason for def-struct is not as a shorthand for creating objects; it is a way of creating records and custom types. The purpose of this is the same as with creating functions that use other functions- abstraction, and the separation of implementation and interface, both of which make future updates and changes to the code easier.

For example, what if in the future your program needs to track a point's z-axis as well? If you add a point-z to a type and give it a default value of 0, previous data that the program generated should not be affected (depending on the language, changes, etc).

OO in newLISP is incredibly limited by the fact that each context is a child of MAIN. There can be no introspection and there can be no hierarchies, or objects with other objects as properties.

This is not a bad thing, of course. OOP is not that wonderful, and it makes for very wordy programs.

What I would rather see is match and unify integrated into function definitions, so that I can bind local variables in a function based on the pattern of arguments passed, as well as break a list into its car/cdr. Here is an example that I wrote to facilitate this:

Code: Select all

(define (unbound? var)
  (and (symbol? var) (not (quote? var)) (nil? (eval var))
       (starts-with (string var) {[A-Z]} 0)))

(define (pattern-to-match-expr pattern)
  (cond
    ((null? pattern) '()) ; base case
    ((and (not (list? pattern)) (unbound? pattern)) '(?)) ; tautology
    ((list? (first pattern)) ; nested list
     (cons (pattern-to-match-expr (first pattern))
           (pattern-to-match-expr (rest pattern))))
    ((match '(? | ?) pattern) ; cons expression
     (unless (for-all unbound? (cons (first pattern) (last pattern)))
             (throw-error (string "Invalid pattern segment: " pattern))
             '(? *)))
    ((unbound? (first pattern)) ;  unbound variable
     (cons '? (pattern-to-match-expr (rest pattern))))
    (true ; other atom
     (cons (first pattern) (pattern-to-match-expr (rest pattern))))))

(define (pattern-to-unify-expr pattern)
  (if (and (not (list? pattern)) (unbound? pattern))
      pattern
      (clean (fn (x) (= '| x)) pattern)))

(define (match-unify)
  (let ((match-expr (pattern-to-match-expr (args 0)))
        (unify-expr (pattern-to-unify-expr (args 0)))
        (lst (args 1)))
       (unify unify-expr (match match-expr lst))))

(define-macro (match-cond)
  (letn ((var (args 0))
         (pattern (args 1 0))
         (guards (if (= 'when (args 1 1 0)) (rest (args 1 1)) '()))
         (expr (if guards (2 (args 1)) (1 (args 1))))
         (binding (match-unify pattern (eval var)))
         (next (2 (args))))
        (cond
          ((and binding (for-all true? (map (fn (g) (eval (expand g binding))) guards)))
           (map eval (expand expr binding)))
          ((null? next)
           (throw-error (string "Match failed against " (eval var))))
          (true (eval (cons 'match-cond (cons var next)))))))
match-cond is like cond, but works like this:

Code: Select all

(setq obj '(some pattern (of lists and stuff)))
(match-cond obj
  ((A B C) (map println '(A B C))))
...with | acting to break a list into the first and rest of its contents. This kind of thing gives newLISP a real edge and makes programs much more concise - as well as giving a function the ability to handle much more complexity without increasing its own complexity much beyond a cond statement. It's like method overloading in functional terms.

This idea would make newLISP functions not just first class objects, but functional powerhouses that replace the entire idea of simulating OOP. Rather than have an object with methods that perform actions on that object, you can simply pass a list around from function to function, with each function knowing how to handle the list depending on its shape.

PS Sorry for the lengthy rant. I just got finished with a rather large Python project that was severely hindered by the fact that Python needs classes to simulate objects of any complexity. Having something like this would have made things much, much more simple for me.
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

Locked