Contexts as Objects

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

Post by cormullion »

Thanks, Lutz. This will repay some further study...

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 »

Hi cormullion!

Your post sent me back to my dusty OOP newLISP experiments. I discovered that the hash table part of the OO equation I had come up with was actually kind of useful and fun to play with on the command line:

Code: Select all

> (load “/lisp/hash.lsp”)
MAIN
> (new HASH 'h1)
h1
> (h1 '(a 1 b '(1 1) c “three“))
((“a“ 1) (“b“ '(1 1)) (“c“ “three“))
> (h1 'b)
'(1 1)
> (h1 'a 99)
((“a“ 99) (“b“ '(1 1)) (“c“ “three“))
> (h1 'strings (fn () (map (fn (ea) (string (ea 0) “ = “ (ea 1))) (h1))))
((“a“ 99) (“b“ '(1 1)) (“c“ “three“) (“strings“ (lambda () (map (lambda 
   (ea) 
   (string (ea 0) “ = “ (ea 1))) 
  (h1)))))
> ((h1 'strings))
(“a = 99“ “b = '(1 1)“ “c = three“ 
  “strings = (lambda () (map (lambda (ea) (string (ea 0) \“ = \“ (ea 1))) (h1)))“)
> (new HASH 'h2)
h2
> (h2 '(a 9 b '(88 77) c “two one“))
((“a“ 9) (“b“ '(88 77)) (“c“ “two one“))
> (h2 'other h1)
((“a“ 9) (“b“ '(88 77)) (“c“ “two one“) (“other“ h1))
> (h2 'string (fn (e) (string ((h2 'other) e) “ “ (h2 e))))
((“a“ 9) (“b“ '(88 77)) (“c“ “two one“) (“other“ h1) (“string“ 
   (lambda (e) (string ((h2 'other) e) “ “ (h2 e)))))
> (map (h2 'string) '(a b c))
(“99 9“ “'(1 1) '(88 77)“ “three two one“)
> (h1 'string (fn () (join (h1 'strings) “ “)) 'print (fn () (println ((h1 'string)) ““)))
((“a“ 99) (“b“ '(1 1)) (“c“ “three“) (“strings“ (lambda () (map (lambda 
   (ea) 
   (string (ea 0) “ = “ (ea 1))) 
  (h1)))) 
 (“string“ (lambda () (join (h1 'strings) “ “))) 
 (“print“ (lambda () (println ((h1 'string)) ““))))
> (new h1 'h3)
h3
> (h3)
((“a“ 99) (“b“ '(1 1)) (“c“ “three“) (“strings“ (lambda () (map (lambda 
    (ea) 
    (string (ea 0) “ = “ (ea 1))) 
   (h1)))) 
 (“string“ (lambda () (join (h1 'strings) “ “))) 
 (“print“ (lambda () (println ((h1 'string)) ““))))
> (= (h1) (h3))
true
> (h3 'a 999)
((“a“ 999) (“b“ '(1 1)) (“c“ “three“) (“strings“ (lambda () (map 
   (lambda (ea) (string (ea 0) “ = “ (ea 1))) 
   (h1)))) 
 (“string“ (lambda () (join (h1 'strings) “ “))) 
 (“print“ (lambda () (println ((h1 'string)) ““))))
> (= (h1) (h3))
nil
> _
You can use numbers as keys:

Code: Select all

> (h2 42 “meaning of life“)
((“a“ 9) (“b“ '(88 77)) (“c“ “two one“) (“other“ h1) (“string“ (lambda 
   (e) 
   (string ((h2 'other) e) “ “ (h2 e)))) 
 (42 “meaning of life“))
> (h2 42)
“meaning of life“
> _
You can even use functions as keys:

Code: Select all

> (h2 + “plus“)
((“a“ 9) (“b“ '(88 77)) (“c“ “two one“) (“other“ h1) (“string“ (lambda 
   (e) 
   (string ((h2 'other) e) “ “ (h2 e)))) 
 (42 “meaning of life“) 
 (+ <E680> “plus“))
> (h2 +)
“plus“
> 
As you can see, these little critters are close to being objects (instance variables and methods), but without things like inheritance or constructors. Every time I try to implement the more complex aspects of OO using newLISP, I give up, wondering if all the object-oriented overhead and complexity is really worth it.

Oh, and before I forget, here's the code for hash.lsp:

Code: Select all

;; @module HASH
;; This module turns humble contexts into haughty hash tables (dictionaries).
;;

(context 'HASH)

;; @syntax HASH:slots
;; Holds the hash table's key/value pairs.

(set 'slots '())

;; @syntax (HASH:HASH)
;; @return The key/value pairs.
;;
;; @syntax (HASH:HASH key)
;; @param <key> The key of the value to return.
;; @return The value of the key, otherwise nil.
;;
;; @syntax (HASH:HASH key value [key value ...])
;; @param <key> The key (can be any expression) of the value to add or replace.
;; @param <value> The value (can be any expression).
;; @return The key/value pairs.
;;
;; This default context function enables contexts to easily act as hash tables.
;;
;; @example
;; > (new HASH 'h1)
;; h1
;; > ;-o no key/value pairs yet
;; > (h1)
;; ()
;; > ;-o initializing h1's key/value pairs with a list
;; > (h1 '(a 1 b 5 c 7)) 
;; ((“a“ 1) (“b“ 5) (“c“ 7))
;; > ;-o getting the value for key “b“
;; > (h1 “b“)
;; 5
;; > ;-o setting “b“ to the new value 11 
;; > ;-o we can use symbols for the keys (they are converted to strings)
;; > (h1 'b 11)
;; ((“a“ 1) (“b“ 11) (“c“ 7))
;; > ;-o adding a new key/value pair
;; > (h1 'd “d's“)
;; ((“a“ 1) (“b“ 11) (“c“ 7) (“d“ “d's“))
;; > ;-o adding multiple key/value pairs
;; > (h1 'string (fn () (join (map string (h1)) “ “)) 'print (fn () (println ((h1 'string)) ““)))
;; ((“a“ 99) (“b“ '(1 1)) (“c“ “three“) (“string“ (lambda () (join 
;;   (map name (h1)) “ “))) 
;;  (“print“ (lambda () (println ((h1 'string)) ““))))
;; > ;-o calling a hash function
;; > ((h1 'print))
;; (“a“ 99) (“b“ '(1 1)) (“c“ “three“) (“string“ (lambda () (join (map string (h1)) “ “))) 
;;  (“print“ (lambda () (println ((h1 'string)) ““)))
;; ““
;; > _

(define (HASH:HASH) 
  (set 
  	'ags (args)
		'symbol-safe
			(fn (maybe-sym)
				(if (symbol? maybe-sym) (name maybe-sym) maybe-sym)
			)
		'arg1 (symbol-safe (ags 0))
		'key-part
			(fn (one-pair)
				(symbol-safe (one-pair 0))
			)
		'replace/add 
			(fn (key/value) 
				(set 
					'key (key-part key/value)
					'replacement (list key (key/value 1))
				)
				(unless (replace-assoc key slots replacement)
					(push replacement slots -1)
				)
				slots
			)
	)
  (case (length ags)
    (0 slots)
    (1 
			(if (list? arg1)
        (set 'slots 
					(map 
						(fn (each-pair) 
							(list (key-part each-pair) (each-pair 1))
						) 
						(explode arg1 2)
					)
				)
        (lookup arg1 slots)
			)
		)
    (2 (replace/add (list arg1 (ags 1))))
		(true 
			(map replace/add (explode ags 2))
			slots
		)
	)
)

(context MAIN)
m i c h a e l

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 »

Hi Lutz!

I've been test-driving your 'def-class' macro a bit and so far it feels pretty natural to use. The only problem is the name: 'def-class' looks too much like Common Lisp! I tried 'class', then switched to 'object'. Maybe 'type', 'kind', or even 'adt' for abstract data type? I think I would even prefer 'define-class' just so it would not look like CL ;-)

m i c h a e l

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

Post by rickyboy »

Jeff wrote: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.
Yes, well, this idea is common in functional languages. Your implementation is akin to what Erlang does with pattern matching in function definitions:

http://erlang.org/course/sequential_pro ... funcsyntax

And I agree, something like this would be very helpful as an intrinsic capability in newlisp.
(λx. x x) (λx. x x)

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

Post by Jeff »

Erlang, ML (OCaml and NJ/ML), et al, all use unification binding. Erlang does it both in function parameter arguments (lambda lists) as well as match/receive syntax. OCaml has a shortcut to allow both methods:

Code: Select all

let regular_function foo bar = (* function with arguments foo and bar *);;

let regular_function = function
  | foo, bar -> (* what to do if we get foo and bar as arguments *)
  | foo -> (* what to do if we just get foo *)
;;
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

m35
Posts: 171
Joined: Wed Feb 14, 2007 12:54 pm

Post by m35 »

m i c h a e l wrote:As you can see, these little critters are close to being objects (instance variables and methods), but without things like inheritance or constructors. Every time I try to implement the more complex aspects of OO using newLISP, I give up, wondering if all the object-oriented overhead and complexity is really worth it.
This OOP in newLISP interests me. When I was trying to duplicate some Python libraries in newLISP, I used a context approach similar to kinghajj's to create objects. It provided a simple but mostly effective way to do inheritance and constructors.

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

Post by Jeff »

You can simulate nested contexts by using a few functions in main to maintain a list of incrementally named symbols that point to contexts:

http://artfulcode.nfshost.com/files/nes ... ewlisp.php
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

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

Post by rickyboy »

Lutz wrote: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.
Excellent Lutz! This is the best idea I've seen so far for data abstraction in newlisp. It's simple and clean.

Let me offer some, admittedly pedantic, advice. I'd change def-class to evaluate to the context (class) name, by adding one more line:

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))
That makes the REPL response easier on the eyes (and doesn't waste value output lines):

Code: Select all

> (def-class (point x y))
point
Also I'd change point:difference to point:distance, and in the body of this function I'd also change the point coordinate references from such as (p1 0) to such as (point:x p1); so that you all in all have the definition:

Code: Select all

(define (point:distance p1 p2)
  (sqrt (add (pow (sub (point:x p2) (point:x p1)) 2)
             (pow (sub (point:y p2) (point:y p1)) 2))))
Thanks Lutz -- I will use your code in my projects. --Rick
(λx. x x) (λx. x x)

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

Post by rickyboy »

Jeff wrote:You can simulate nested contexts by using a few functions in main to maintain a list of incrementally named symbols that point to contexts:

http://artfulcode.nfshost.com/files/nes ... ewlisp.php
Good article, Jeff. I noticed that your definition of function @, namely

Code: Select all

(define (@ tree)
  (cond
    ((null? tree) nil)
    ((>= (length tree) 2)
      (if (> (length tree) 2)
          (@ (cons (context (tree 0) (name (tree 1))) (2 tree)))
          (context (tree 0) (name (tree 1)))))))
is, in essence, doing a left fold over the list (which you call tree) with the function
(λ (acc x) (context acc (name x)))
So I'd rewrite @ to use apply (a left fold operator, by definition):

Code: Select all

(define (@ lst)
  (if (and (list? lst) (> (length lst) 1))
      (apply (fn (acc x) (context acc (name x))) lst 2)))
(λx. x x) (λx. x x)

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

Post by Jeff »

Yeah. It was while I was exploring contexts, which was before I explored apply and letex. I had a hard time getting apply- I thought it was funcall, not a folding operation. At any rate, I offered up the article for the topic; I don't use that in my code. I only use contexts sparingly now.
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

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

Post by Lutz »

Thanks for the good suggestions Rick (return value for def-class and point:distance). There was also a suggestion by Michael to change the name from 'def-class' to something else, but I couldn't think of anything else and I have seen 'def-class' used in a similar way by other people and in other Lisps. In any case I am open to suggestions for a different name.
Jeff wrote:I only use contexts sparingly now.
... and so do most, including my self, using contexts mainly for organizing code into modules, and this is what contexts in newLISP where designed for originally. The suggested 'def-class' function falls in that same category: it creates a module of specialized functions for a certain data type.

As an additional benefit 'def-class' helps in data abstraction and gives a bit of an OO look and feel when reading/writing the code. That "class" module created supplies a minimal set of data constructor and access functions.

For me contexts have one other mayor application area, which is using them as containers for dictionaries and hashes when dealing with huge amounts of data (thousands to millions) in natural language processing and often together with the 'bayes-train' and 'bayes-query' functions.

Perhaps the current 'newLISP Users Manual' is pushing the OO aspect of contexts too much and should more emphasize usage of contexts for organizing functions in modules and for data abstraction?

Lutz

ps: edited the original post from 10/4 with the context as return value.
Last edited by Lutz on Fri Oct 05, 2007 4:06 pm, edited 1 time in total.

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 »

m35 wrote:When I was trying to duplicate some Python libraries in newLISP, I used a context approach similar to kinghajj's to create objects.
m35 elsewhere wrote:I have been porting some code from Python to newlisp during the last few weeks
This brings up a good point. The porting over of one's code seems a common enough task when first trying a new language. And if your code is object-oriented and the new language is functional, you're faced with having to convert your code into a completely different way of programming. Do we yank out an object's methods into functions containing long case statements? How do we go from thinking of our code as a collection of interacting objects to thinking in terms of functions applied to data? I have yet to read, hear, or see anyone give a clear explanation of just how to do this. I'm afraid the functional/object-oriented paradigms reflect a deep difference in the way people think about problems. Afraid because I may be doomed to endlessly trying to create a decent object system within newLISP! ;-)

m i c h a e l

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 »

Lutz wrote:The suggested 'def-class' function falls in that same category: it creates a module of specialized functions for a certain data type.
Since you used the term 'data type' to describe what the functions in the module are for, what about using 'data-type'?

Code: Select all

> (data-type (person last first))
person
> (data-type (book title author isbn))
book
> (set 'catch-22 (book “Catch-22” (person “Heller” “Joseph”) “0-684-83339-5”))
(“Catch-22” (“Heller” “Joseph”) “0-684-83339-5”)
> (person:first (book:author catch-22))
Joseph
> _
It's the same number of characters as 'def-class' and doesn't look at all like Common Lisp! (I began to loathe CL after reading all the unfair and condescending comments the CLers were making about newLISP.)

m i c h a e l

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

Post by rickyboy »

Very nice suggestion, m i c h a e l. ML-like languages use the data keyword to introduce algebraic data types, so you and Lutz are thinking along those lines. For that reason, data-type (or data) makes sense to me too, and I'd vote for either of them as constructor names.

BTW, I wouldn't hate CL because of it's vocal adherents -- it's a really nice language and has its place in some settings. The pompous a-holes though, oh my ... I agree with you there. :-)
(λx. x x) (λx. x x)

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 »

Rick wrote:Very nice suggestion, m i c h a e l.
Thank you! :-)
Rick wrote:BTW, I wouldn't hate CL because of it's vocal adherents -- it's a really nice language and has its place in some settings.
Yes, that's true. Don't worry, I know it's not CL's fault ;-) I just wonder how a language affects the people who use it, or if the language is affected by them. Probably both. It's clear to me that programming languages attract certain types of people. Ruby and newLISP draw fun-loving and easygoing types, while something like C++ or Common Lisp attracts the “serious” and “superior” programmers. Yes, these are gross oversimplifications, but there may be a grain of truth to them.

m i c h a e l

frontera000
Posts: 72
Joined: Sun Jun 11, 2006 8:02 pm
Location: berkeley, california
Contact:

data-type

Post by frontera000 »

I really like data-type.

One thing I noticed is how it maps to SXML. This way it can be used for defining data-type which can be turned into XML on the fly, which can give new capabilities to newLISP applications.

I really hope this becomes part of the language.

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

Re: data-type

Post by rickyboy »

Welcome back Bob!

BTW, m i c h a e l started another thread which continues our thoughts and experiments with data-type. We discovered some limitations with it, in particular. If you are interested and have some time, we sure could use your brain power. Check out the continuation thread at http://www.alh.net/newlisp/phpbb/viewtopic.php?t=1933.

--Ricky
(λx. x x) (λx. x x)

itistoday
Posts: 429
Joined: Sun Dec 02, 2007 5:10 pm
Contact:

Post by itistoday »

Hi, I'm a newLISP newBIE and while trying to understand Lutz's awesome macro I went through and formatted it nicely in C-style and added comments. If there are any other newbies out there that are confused by it perhaps this may help:

Code: Select all

(define-macro (data-type)
	(let (ctx (context (args 0 0)))
		; sets the constructor
    	(set (default ctx) (lambda () (args)))
		; create getters and setters
    	(dolist (item (rest (args 0)))
			
			(set (sym item ctx) ; creates something like point:x
				
				; the reason expand is used is because we want the
				; point:x method to print stuff based on a number,
				; not the value of a variable $idx when it's called.
				; In other words it's the difference between:
				; (print (my-list $idx))
				; and
				; (print (my-list 5))
				
				(expand '(lambda-macro (_str _val)
						(if (set '_val (eval _val))
							; if value is specified act as a setter
          					(nth-set ((eval _str) $idx) _val)
							; otherwise act as a getter
          					((eval _str) $idx)
						)
					)
					; internal var representing current index
					; for dolist expression
					'$idx
				)
			)
		)
		ctx
	)
)

Locked