Discussion:
How would you call this programming construct?
(too old to reply)
Mirko Vukovic
2019-10-19 03:11:34 UTC
Permalink
Hello,

As an example, consider a function that adds two numbers.

I want to call it by passing either two numbers, or functions
of zero arguments that evaluate to numbers:

(add-two 2 3)
or
(add-two 2 (lambda () 3))
or
(add-two (lambda() 2) (lambda () 3))

I will show my implimentation below. My questions are:
- Is there a name for this concept
- Is there a better way of doing it

My evantual goal is to do Monte Carlo runs on such functions.
The argument functions will return values generated by
random number generators.

Here is the definition of add-two:

(defun add-two (a b)
"Add two numbers

The arguments can hold either numeric values or functions of
zero arguments that return numeric values"
(let ((a (if (typep a 'function)
(funcall a)
a))
(b (if (typep b 'function)
(funcall b)
b)))
(+ a b)))

Thanks,

Mirko
Robert L.
2019-10-19 06:17:48 UTC
Permalink
Post by Mirko Vukovic
As an example, consider a function that adds two numbers.
I want to call it by passing either two numbers, or functions
(add-two 2 3)
or
(add-two 2 (lambda () 3))
or
(add-two (lambda() 2) (lambda () 3))
- Is there a name for this concept
- Is there a better way of doing it
My evantual goal is to do Monte Carlo runs on such functions.
The argument functions will return values generated by
random number generators.
(defun add-two (a b)
"Add two numbers
The arguments can hold either numeric values or functions of
zero arguments that return numeric values"
(let ((a (if (typep a 'function)
(funcall a)
a))
(b (if (typep b 'function)
(funcall b)
b)))
(+ a b)))
(define (add . numbers)
(apply +
(map
(lambda (x) (if (procedure? x) (x) x))
numbers)))

(add 1 (lambda() 3) 5 7 9)
===>
25
Vladimir Sedach
2019-10-19 17:23:13 UTC
Permalink
Post by Mirko Vukovic
- Is there a name for this concept
Polymorphism
Post by Mirko Vukovic
- Is there a better way of doing it
My evantual goal is to do Monte Carlo runs on such functions.
The argument functions will return values generated by
random number generators.
The best way is to generate the random numbers before calling the
function. If for some reason you really need to pass around a value
that means "yield a random number when a value is requested," you can
make things easier to debug and avoid overhead related to allocating
and calling functions by passing around something simple like a
symbol (or maybe a RANDOM-STATE object), and writing a function that
knows what to do with it:

(defun yield-number (x)
(if (eq x 'random)
(random 999999)
x))

Then:

(defun add-two (a b)
(+ (yield-number a) (yield-number b)))

YIELD-NUMBER can be declared inline.
--
Vladimir Sedach
Software engineering services in Los Angeles https://oneofus.la
smh
2019-10-21 09:19:30 UTC
Permalink
The polymorphism could alternatively be implemented by writing it as a gf with 4 specialized methods.

An entirely different useful generalization could be to code the function to accept an arbitrary number (zero or more) of arguments, like cl:+ does.
Mirko Vukovic
2019-10-21 18:25:43 UTC
Permalink
Post by smh
The polymorphism could alternatively be implemented by writing it as a gf with 4 specialized methods.
An entirely different useful generalization could be to code the function to accept an arbitrary number (zero or more) of arguments, like cl:+ does.
Agreed that for this case I could implement it as a GF.

However, this is toy problem, and in some cases I may have more
or less arguments.

(I will amend my original post to explain why I was playing with this
construct)
Kaz Kylheku
2019-10-21 03:22:25 UTC
Permalink
Post by Mirko Vukovic
Hello,
As an example, consider a function that adds two numbers.
I want to call it by passing either two numbers, or functions
(add-two 2 3)
or
(add-two 2 (lambda () 3))
or
(add-two (lambda() 2) (lambda () 3))
What you're doing is implementing lazy evaluation and explicitly
passing the "thunks" needed to make it work, without any syntatic
sugar. Additionally, you have non-functional terms for simple
constants.
Mirko Vukovic
2019-10-21 18:23:45 UTC
Permalink
Post by Kaz Kylheku
Post by Mirko Vukovic
Hello,
As an example, consider a function that adds two numbers.
I want to call it by passing either two numbers, or functions
(add-two 2 3)
or
(add-two 2 (lambda () 3))
or
(add-two (lambda() 2) (lambda () 3))
What you're doing is implementing lazy evaluation and explicitly
passing the "thunks" needed to make it work, without any syntatic
sugar. Additionally, you have non-functional terms for simple
constants.
I do not follow the "non-functional terms for simple constants"

I see that when I pass two numbers, the call is functional, whereas if
I pass one or more functions, it is not. Is that what you mean?
Kaz Kylheku
2019-10-22 01:27:00 UTC
Permalink
Post by Mirko Vukovic
Post by Kaz Kylheku
Post by Mirko Vukovic
Hello,
As an example, consider a function that adds two numbers.
I want to call it by passing either two numbers, or functions
(add-two 2 3)
or
(add-two 2 (lambda () 3))
or
(add-two (lambda() 2) (lambda () 3))
What you're doing is implementing lazy evaluation and explicitly
passing the "thunks" needed to make it work, without any syntatic
sugar. Additionally, you have non-functional terms for simple
constants.
I do not follow the "non-functional terms for simple constants"
I see that when I pass two numbers, the call is functional, whereas if
I pass one or more functions, it is not. Is that what you mean?
Yes; I mean you have two representations of 3: at the top level
in the program, we can specify just 3 or the wrappage (lambda () 3).
These substitute for each other, and in the lower evaluation level, they
reduce to the same thing.

Of course (lambda () 3) itself just has "just 3" in it.

But let's think for a moment of a program transformation tool which
generates this stuff from a notation where you don't see the lambdas.
That tool could translate a complex expression EXPR into (lambda ()
EXPR), thereby delaying its evaluation. But for a simple constant,
it might dispense with the lambda wrapping.
Mirko Vukovic
2019-10-21 18:29:17 UTC
Permalink
Post by Mirko Vukovic
Hello,
As an example, consider a function that adds two numbers.
I want to call it by passing either two numbers, or functions
(add-two 2 3)
or
(add-two 2 (lambda () 3))
or
(add-two (lambda() 2) (lambda () 3))
- Is there a name for this concept
- Is there a better way of doing it
My evantual goal is to do Monte Carlo runs on such functions.
The argument functions will return values generated by
random number generators.
(defun add-two (a b)
"Add two numbers
The arguments can hold either numeric values or functions of
zero arguments that return numeric values"
(let ((a (if (typep a 'function)
(funcall a)
a))
(b (if (typep b 'function)
(funcall b)
b)))
(+ a b)))
Thanks,
Mirko
Let me explain why I am going this way:

I want to be able to subject the guts of my function to unit tests,
and then with a minimum of fuss do a Monte Carlo simulation on the
same function.

In this approach, I would use a macro "DEFSFUN name (a b ...)" that
would accept only arguments (no keywords, optional, default values),
and that would expand the arguments and body to the above shown form
that can handle numbers or functions as arguments.

Mirko
smh
2019-10-21 23:39:44 UTC
Permalink
cl-user(14): (defun add (&rest rest)
(declare (dynamic-extent rest))
(add* 0 (car rest) (cdr rest))) ; avoid O^2 repeated &rest list consing
add
cl-user(15): (defmethod add* (sofar (next function) rest)
(add* (+ sofar (funcall next)) (car rest) (cdr rest)))
#<standard-method add* (t function t)>
cl-user(16): (defmethod add* (sofar (next number) rest)
(add* (+ sofar next) (car rest) (cdr rest)))
#<standard-method add* (t number t)>
;; Add additional methods for any classes of interest.
cl-user(17): (defmethod add* (sofar (next null) rest) ; NIL ends recursion
sofar)
#<standard-method add* (t null t)>
cl-user(18): (add 1 2 3 4 5 6)
21
cl-user(19): (add 1 2 3 (lambda () 4) 5 6)
21
cl-user(20): (add 1 2 3 (lambda () 4) #c(5.5 0.5) 11/2)
#C(21.0 0.5)
Mirko Vukovic
2019-10-22 00:53:32 UTC
Permalink
Post by smh
cl-user(14): (defun add (&rest rest)
(declare (dynamic-extent rest))
(add* 0 (car rest) (cdr rest))) ; avoid O^2 repeated &rest list consing
add
cl-user(15): (defmethod add* (sofar (next function) rest)
(add* (+ sofar (funcall next)) (car rest) (cdr rest)))
#<standard-method add* (t function t)>
cl-user(16): (defmethod add* (sofar (next number) rest)
(add* (+ sofar next) (car rest) (cdr rest)))
#<standard-method add* (t number t)>
;; Add additional methods for any classes of interest.
cl-user(17): (defmethod add* (sofar (next null) rest) ; NIL ends recursion
sofar)
#<standard-method add* (t null t)>
cl-user(18): (add 1 2 3 4 5 6)
21
cl-user(19): (add 1 2 3 (lambda () 4) 5 6)
21
cl-user(20): (add 1 2 3 (lambda () 4) #c(5.5 0.5) 11/2)
#C(21.0 0.5)
Nice example. Thanks!
t***@google.com
2019-10-22 18:37:44 UTC
Permalink
Post by Mirko Vukovic
Post by Mirko Vukovic
Hello,
As an example, consider a function that adds two numbers.
I want to call it by passing either two numbers, or functions
(add-two 2 3)
or
(add-two 2 (lambda () 3))
or
(add-two (lambda() 2) (lambda () 3))
- Is there a name for this concept
- Is there a better way of doing it
My evantual goal is to do Monte Carlo runs on such functions.
The argument functions will return values generated by
random number generators.
(defun add-two (a b)
"Add two numbers
The arguments can hold either numeric values or functions of
zero arguments that return numeric values"
(let ((a (if (typep a 'function)
(funcall a)
a))
(b (if (typep b 'function)
(funcall b)
b)))
(+ a b)))
Thanks,
Mirko
I want to be able to subject the guts of my function to unit tests,
and then with a minimum of fuss do a Monte Carlo simulation on the
same function.
If your main use case is to support unit tests, then perhaps the following
would be helpful.

(a) If the basic function is implemented as a (default) generic function, you
could add an :AROUND method that could handle the function calling if
necessary. That would allow you to keep the underlying function simple and
only add the additional overhead while doing unit tests or monte carlo
simulations.
(b) Some implementations have an ADVICE feature that allows you to add a
wrapper around a regular function. This would not, of course, be portable
unless you wrote your own version of it.
Robert Munyer
2019-12-16 23:54:56 UTC
Permalink
Post by Mirko Vukovic
Hello,
As an example, consider a function that adds two numbers.
I want to call it by passing either two numbers, or functions
(add-two 2 3)
or
(add-two 2 (lambda () 3))
or
(add-two (lambda() 2) (lambda () 3))
- Is there a name for this concept
- Is there a better way of doing it
I don't think that exact construct is popular enough to have a name.
You may want to read about "futures", "promises" and "thunks" which
are related to your construct and may give you some good ideas.
--
-- Robert Munyer code below generates e-mail address

(format nil "~(~{~a~^ ~}~)" (reverse `(com dot munyer at ,(* 175811 53922))))
Jeff Barnett
2019-12-17 00:08:54 UTC
Permalink
Post by Robert Munyer
Post by Mirko Vukovic
Hello,
As an example, consider a function that adds two numbers.
I want to call it by passing either two numbers, or functions
(add-two 2 3)
or
(add-two 2 (lambda () 3))
or
(add-two (lambda() 2) (lambda () 3))
- Is there a name for this concept
- Is there a better way of doing it
I don't think that exact construct is popular enough to have a name.
You may want to read about "futures", "promises" and "thunks" which
are related to your construct and may give you some good ideas.
It seems to me that this closely resembles a generic function where the
implementation depends on the type/class of the arguments.
--
Jeff Barnett
Loading...