You can use lexical scoping to store private state for an object (not accessible via (= thing!foo 'bar) ), and so that your object's functions can call each other without requiring any kind of qualifying prefix. Here's a contrived example of a "shouter" that can be started, stopped, and shouts stuff when started.
"this" is defined so that 'equals will work (otherwise the functions have no way to reference the object that contains them); 'prname can call 'shout directly. 'name and 'started are hidden - 'name cannot be modified, and 'started can only change via the "public" 'start and 'stop functions.
The right macro can disappear all the boilerplate:
(def shouter (name)
(with ((this started) nil)
(= this (make-obj
(start () (assert started))
(stop () (wipe started))
(shout (s) (if started (prn (upcase s))))
(prname () (shout name))
(equals (x) (is this x)) ))
this))
Coming from javaland, I fear I'm trying to hack objects onto arc and failing to see the One True Way with functions. But this macro has been convenient for interfacing with java. I guess some seriously tricky macroing would allow inheritance ... this is left as an exercise for the reader :)
The real problem with storing the functions in the object, I think, is that you have different copies of the function for every object. There are two problems with this: (a) it wastes space, and (b) if you redefine the function, you have to recreate all your objects. That's the advantage of storing methods externally.
Also, speaking of "if it's an idiom, [it] probably needs a macro," I often find myself writing (obj name1 name1 name2 name2 name3 name3 ...), like you have above. So here's my nobj macro to solve that problem; for the same effect as above, just write (nobj name1 name2 name3 ...):
(mac nobj args
" Creates a table from the list of variables passed to it; each variable's
name is keyed to its value. E.g. if x = 10 and y = -10, then (nobj x y)
results in #hash((y . -10) (x . 10)).
See also [[obj]] [[table]] "
`(obj ,@(flat:map [list _ _] args)))
well, we can closure the methods with the constructor function so they aren't duplicated, at the expense of explicitly requiring the object to be passed to them, or some kind of dispatch mechanism. we can also implement simple inheritance:
with (apply or ...) we could have a long inheritance chain, but here i have just two objects so the pattern is clear. we can then define objects like so:
(let _proto (obj
full (fn (self) (string self!first " " self!last))
until (fn (self year) (- year self!age)))
(def person (first last age)
(inherits (obj
_proto _proto
first first
last last
age age)
_proto))
)
arc> (= p (person "joe" "momma" 18))
#<procedure>
arc> (p!full p)
"joe momma"
arc> (p!until p 22)
4
arc> (= q (person "axe" "murderer" 10))
#<procedure>
arc> (q!full q)
"axe murderer"
arc> (q!until q 22)
12
because the inheritance is dynamically dispatched or whatitbe, we can alter the methods with the intended effects:
arc> (q!backwards q)
Error: "Function call on inappropriate object nil (#<procedure>)"
arc> (= ((q '_proto) 'backwards) (fn (self) (string self!last " " self!first)))
#<procedure:gs2439>
arc> (q!backwards q)
"murderer axe"
arc> (p!backwards p)
"momma joe"
arc> (= ((q '_proto) 'until) (fn (self age) "a long time"))
#<procedure:gs2451>
arc> (q!until q 22)
"a long time"
arc> (p!until p 18)
"a long time"
note the 'inherits' function is agnostic. we can inherit from arbitrary objects and functions or what have you, just that in this case it was used to inherit from a hidden prototype
anarki supposedly has user definable syntatic sugaries, so the (q!blah q) pattern could be sugarized
Does it really store a new copy of the function each time? I thought it would only store the closure, and apparently closures are cheap ( http://arclanguage.org/item?id=7342 ) (sorry almkglor, I end up quoting you all the time).
And as for redefining functions: once your api is stable there's probably less need to redefine functions, and if necessary you can still
(= p!until (fn () ...))
(although this way you don't have access to "private" variables in the lexical scope of the original. I'm completely with EliAndrewC in preferring (p!until ...) over (person-until p ...)
And thanks for nobj, it's awesome. It makes my macro look like Visual Basic. Slowly, I learn ...
Yes, a good implementation should store just the closed variables and a reference to the code - there shouldn't be any code duplication.
It thus depends on how many local variables are being closed over. Note that in some implementations (although not in arc2c, and by inference not in SNAP) a closure is just two pointers: a reference to an environment and a reference to the code. Of course each 'let form and function would create a new environment though, and this style is not so often used because lookup of closed variables can require indirection.
You're welcome---I'm glad to have been of assistance.
As for storing copies, I would have said that it would store extra copies because of the different variable it closes over, but almkglor points out that you can separate code and environment, so the question is what mzscheme does.
The thing about redefinition is that in, say, Ruby, you can do
class String
def foo
code_goes_here
end
end
And every string will have that new foo method. Here, you can only redefine the methods of one object.
For me, the real syntax question is whether we want (until p age) (which probably means that we are using the CLOS model of generic functions) or we want (p!until age) (which probably means that we are using the Smalltalk model of message passing). I sort of like the former syntax, but I also sort of prefer the Smalltalk model. What do you think?
Note that redefinition using (p!until age) syntax is still possible in Arc using 'defcall and if you predeclare the private variables.
For instance, consider this:
(deftype foo (x)
(private y z)
(meth niaw ()
(do-something x y z))
(meth arf (something)
(do-something-else something x y z)))
=>
(let methods
(table
; lambda lifted!
'niaw
(fn (x y z)
(do-something x y z))
'arf
(fn (x y z something)
(do-something-else something x y z)))
(def foo-replace-method (s f)
(= (methods s) f))
; so external code can determine the local variables
(def foo-get-private-variables ()
'(x y z))
(def foo (x)
(with (y nil z nil)
(let invoker
(fn (f rest)
(apply f x y z rest))
(fn (which-method)
(aif
(methods which-method)
(fn rest (invoker it rest)))))))
(mac nobj args
" Creates a table from the list of variables passed to it; each variable's
name is keyed to its value. E.g. if x = 10 and y = -10, then (nobj x y)
results in #hash((y . -10) (x . 10)).
See also [[obj]] [[table]] "
`(obj ,@(mappend [list _ _] args)))
^^ Of course you don't: just tell Arc about what you do remember, and it'll tell you more about related things you might want to look at too:
arc> (help map)
(from "arc.arc")
[fn] (map f . seqs)
Applies the elements of the sequences to the given function.
Returns a sequence containing the results of the function.
See also [[each]] [[mapeach]] [[map1]] [[mappend]] [[andmap]]
[[ormap]] [[reduce]]
Documentation for saferead (from "arc.arc")
[fn] (saferead arg)
Reads an expression, blocking any errors.
Use (quit) to quit, (tl) to return here after an interrupt.
arc>