gendl
gendl copied to clipboard
functions vs computed-slots?
IMPORTANT!!!
In order to make clearer to Gendl users that functions don't behave in a similar way to (computed-) slots, I strongly suggest to make the difference clearer in the code and exploit dependency tracking better. The slots below (just a few examples, the list can be almost endless) are the result of a function evaluation, however interpreted and consequently used by the layman user as computed slot with expected associated behavior:
- :total-length on curve
- :volume on brep
- :mass on brep
Instead of causing this confusion, create new functions:
- assess-total-length
- assess-volume
- assess-mass
with their counterparts being computed-slots:
- :total-length message on curve according to
- :volume message on breps according to
- :mass message on bresp according to
This will make sure that the user will always refer to slots and consequently, things are not recomputed unnecessarily over and over again if dependencies didn't change. Example below demonstrates that misuse may have huge performance impacts:
(in-package :gdl-user)
(define-object test-object (box-solid)
:input-slots
((width 2)
(length 3)
(height 4))
:computed-slots
((volume-computed (the volume))
)
)
gdl-user> (make-self 'test-object)
#<test-object @ #x288afb72>
gdl-user> (time (dotimes (i 10000) (the volume)))
; cpu time (non-gc) 35.615029 sec user, 0.093601 sec system
; cpu time (gc) 0.031200 sec user, 0.000000 sec system
; cpu time (total) 35.646229 sec user, 0.093601 sec system
; real time 107.751000 sec (00:01:47.751000) (33.17%)
; space allocation:
; 721,758 cons cells, 10,461,192 other bytes, 0 static bytes
; Page Faults: major: 0 (gc: 0), minor: 0 (gc: 0)
gdl-user> (time (dotimes (i 10000) (the volume-computed)))
; cpu time (non-gc) 0.000000 sec user, 0.000000 sec system
; cpu time (gc) 0.000000 sec user, 0.000000 sec system
; cpu time (total) 0.000000 sec user, 0.000000 sec system
; real time 0.005000 sec ( 0.0%)
; space allocation:
; 81 cons cells, 1,024 other bytes, 0 static bytes
; Page Faults: major: 0 (gc: 0), minor: 0 (gc: 0)
The reason most things are functions rather than computed-slots is because they can accept arguments, so they can return different return-values if they are called with different values for the arguments. This eliminates the possibility of caching, unless we introduce :cached-functions, which are available already in Gendl, but on an experimental basis (they have not been exercised heavily yet).
In addition to thinking about some kind of naming conventions, I think it would also be helpful to extend Slime's function completion to work with Gendl functions, so it will be obvious when entering a Gendl function that it is indeed a function (and not a simple computed-slot), and what the arguments are. This work was started several years ago before we had really rolled out Slime as a default environment, and it is time now to dust it off and make it very much available as part of the environment!
As you mention, an obvious alternative to cached-functions, where an application wants to call a Gendl function with a particular set of arguments (and arguably simpler/clearer than a cached function) is to establish a new normal (cached) computed-slot to catch (and cache) the return-value of the function call.
I think that would be very useful and most sincere to the user, giving him/her exactly what he wants: ultimate speed with the ability to still flexibly call functions with non-default values for the inputs. Note that I assume that the computed-slots here call the associated function with the (as can be reasonably expected) default values for the function inputs.
Ever considered memoize hacks for lisp? I feel it might cause extra delays for rarely used functions, in which cases it may not pay off.
http://www.common-lisp.net/project/araneida/araneida-release/memoization.lisp
Here's an example of how you make a cached-function (which does memoization) in Gendl (this is experimental and undocumented, but has actually been in there for a long time!):
(define-object cached-try ()
:functions ((factorial :cached
(n)
(if (= n 0) 1 (* n (the (factorial (1- n))))))))
The alternative I mentioned above is that (as it stands today), the user can go ahead and establish a new computed-slot to cache results when desired, if one does not already exist built-in to the Gendl primitive in question.
No matter how many cached "catcher" slots we add, there will still always be uncached functions in Gendl, and the opportunity for users to call these unknowingly. So the main point is to give the user a clear way of knowing when such a function is being used, so it will not be mistaken for a cached slot. Normally, the uniform syntax is a good thing - during initial development you don't want to have to think too much about such things, or have to use different syntax or some contrived naming convention to distinguish cached slots from functions. But for sure the user needs some easy transparent notification. I think Slime feedback would be one effective way of doing that, at least for the users who choose to use Slime.
Another possibility would be a compiler switch to turn on warnings when Gendl functions are being called with no arguments -- something like "Uncached function called with no arguments -- are you sure you don't want a cached slot to catch that value?" --- and maybe even auto-generate such a slot.
A lot to think about. But first steps will be to make a way to loudly notify the user, before trying too much clever automation which (if not done perfectly) could confuse matters even worse...