Contexts as Objects
-
- Posts: 2038
- Joined: Tue Nov 29, 2005 8:28 pm
- Location: latiitude 50N longitude 3W
- Contact:
-
- Posts: 394
- Joined: Wed Apr 26, 2006 3:37 am
- Location: Oregon, USA
- Contact:
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:
You can use numbers as keys:
You can even use functions as keys:
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:
m i c h a e l
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
> _
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“
> _
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“
>
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)
-
- Posts: 394
- Joined: Wed Apr 26, 2006 3:37 am
- Location: Oregon, USA
- Contact:
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
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
Yes, well, this idea is common in functional languages. Your implementation is akin to what Erlang does with pattern matching in function definitions: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.
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)
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 *)
;;
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.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.
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
http://artfulcode.nfshost.com/files/nes ... ewlisp.php
Excellent Lutz! This is the best idea I've seen so far for data abstraction in newlisp. It's simple and clean.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.
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))
Code: Select all
> (def-class (point x y))
point
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))))
(λx. x x) (λx. x x)
Good article, Jeff. I noticed that your definition of function @, namelyJeff 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
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)))))))
So I'd rewrite @ to use apply (a left fold operator, by definition):(λ (acc x) (context acc (name x)))
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)
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.
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.
... 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.Jeff wrote:I only use contexts sparingly now.
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.
-
- Posts: 394
- Joined: Wed Apr 26, 2006 3:37 am
- Location: Oregon, USA
- Contact:
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.
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! ;-)m35 elsewhere wrote:I have been porting some code from Python to newlisp during the last few weeks
m i c h a e l
-
- Posts: 394
- Joined: Wed Apr 26, 2006 3:37 am
- Location: Oregon, USA
- Contact:
Since you used the term 'data type' to describe what the functions in the module are for, what about using 'data-type'?Lutz wrote:The suggested 'def-class' function falls in that same category: it creates a module of specialized functions for a certain 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
> _
m i c h a e l
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. :-)
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)
-
- Posts: 394
- Joined: Wed Apr 26, 2006 3:37 am
- Location: Oregon, USA
- Contact:
Thank you! :-)Rick wrote:Very nice suggestion, m i c h a e l.
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.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.
m i c h a e l
-
- Posts: 72
- Joined: Sun Jun 11, 2006 8:02 pm
- Location: berkeley, california
- Contact:
data-type
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.
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.
Re: data-type
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
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)
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
)
)