I'm a bit distracted with another project at the moment, but I plan on returning to Sage shortly. Thanks for the answers.
freak720 wrote:
1. Any argument may be accessed by its name. Default values can be defined as name=value in the arg list, not sure if this is what you meant by optional, but it can probably be used to implement whatever you did mean.
Pretty much. If an arg has been defined using a name value pair (ie name=value) and I leave the arg out of a function call, it will receive the default value, right?
freak720 wrote:
Not sure what you mean by multi-length args, but you can use *name or **name in the function definition to match unmatched arguments(*name puts extra positional arguments from a call in a tuple, **name puts extra keyword arguments in a dictionary).
No, that isn't what I meant, but that is a very interesting language feature. I'm going to have to think about some uses for it.
What I meant is easily demonstrated using an example. Say I need to write a function to sum integers, but I want it to be able to sum more than two integers because I don't want to have to write code that looks like this...
Code:
(sum 1 (sum 2 3))
... just to sum 1, 2 and 3. Multiple-length args allows me to write the function so that the last argument is basically a list of values and then I can sum (or do whatever) across the list.
freak720 wrote:
2. and 4. In python, functions are also objects, that can be passed around at will. to use a function as an object, simply omit the ().
Nice... I like the get()() example. It is better if you say that functions are "first-class objects" which helps eliminate any confusion with OOP terminology.
freak720 wrote:
3. I think I know what you're talking about. Actually, lst*2 does not evaluate to a list with each element *2'ed, instead, it is one list containing the elements of lst twice.
Yeah, I wrote that and the next day it started bugging me, so I downloaded a new copy of Python for my laptop and saw the same thing. I think that Sage has added some type of list comprehensions to make mathematical operations like the example I gave easier within a limited GUI environment.
freak720 wrote:
I don't quite know what you mean with function composition, an example might help (it doesn't have to do anything useful, just demonstrate the similarity to composite functions in mathematics).
Sure. I should start by saying there is a decent wikipedia article on this subject (w/ an example in Python). One way to think of function composition is the idea of building a function from two or more functions. To keep things simple, we'll combine two functions, but in practice, you tend to get the most bang for you buck when you need to build with more than just two functions. This doesn't really address why you'd want to use composition though. The best answer that I can give is that the ability to compose functions makes a language more orthogonal (in case you don't know what I meant by orthogonal... children's building blocks are highly orthogonal, they work with each other so you can build a wide variety of things with them compared to say the furniture in a room which probably doesn't 'fit together' too well).
Another reason is you can write clearer code by eliminating multiple function calls (eg get()() in Python or (funcall (funcall get)) in Lisp). Now the get()() isn't too bad in your small example, but it would hurt the appearance of your code if you had more function calls (eg get()()()() esp if there are actual args) or if you're using it repeatedly. It tends to be less orthogonal when working with other language features. For example, if I'm using map to apply functions to a list of values, I would have to write a lambda function that calls get()(). OTOH, if I've used a function builder, I could just call the built function which is equivalent of get()(). Another reason for using a function builder is that we might not know which function to use until run-time. I don't necessarily mean this in a polymorphism sense either (ie which method should be called), I mean we might not know which functions until run-time (eg the user defines the function).
Here is fairly simple example. We're going to use print, the built-in timer function, and a sleep function. Let's say that I'm working with some external entity and sometimes I need to add a 1 second delay and other times a 2 second delay between sending messages. We'll say that our messages are sent when I print them. The protocol has "hello", "query" and "end" which take 1s, 2s and 1s, respectively. Let's say that my first script does something like... hello, query1, query2, end. Now the most obvious thing is to write a script like this...
Code:
(defun script ()
(print 'hello)
(sleep 1)
(print 'query)
(sleep 2)
(print 'query)
(sleep 2)
(print 'end)
(sleep 1))
Aside from the problem of boring you into a coma, there are some real problems with maintaining something like this script. First, in a longer script, there is a good chance that a typo will mean that you're not delaying the correct amount of time. Second, you can't very well work in an interactive environment in this manner, right? You'd send the command then have to use a stop watch or something... or type out the command (not starting it) and then the delay, then start both of them. Ugh. I don't know about you, but I don't do data entry!
We could write a function that takes a list of commands, iterates through it and does the correct amount of delay. This is a giant improvement, but it doesn't solve the interactive environment problem unless we know exactly everything in advance. It also isn't really orthogonal to the language. We're introducing a new piece of furniture, not something that plays nice with our existing blocks. It also another piece of code for us to maintain and the rather cheesy implementation I've done doesn't scale very well with dozens of additional commands.
Code:
(defun foo (cmds)
(dolist (cmd cmds)
(print cmd)
(if (or (eq x 'hello)
(eq x 'end))
(sleep 1)
(sleep 2))))
I could break this apart and write individual functions like (defun hello () (print 'hello) (sleep 2)), which lets me do something like (hello)(query)(query)(end) and is than even foo, but I'm repeating the same basic code for each command. If there are a hundred commands, I'm still doing data entry, right? And what if some of the commands are more complex, and require additional things like checking for a response? Then both the foo script starts to break down and the individual functions per command becomes even more tedious.
What if we could write a function that built the function for us? Each command would then be a function call. If a command changed, I simply need to rebuild the one function. They're not all intertwined. Plus, I don't need to update any of the scripts because they'd simply be calling the new functions instead. Also, they're now building blocks that play nice with the rest of the language.
Code:
CL-USER> (defun make-cmd (cmd delay)
#'(lambda ()
(print cmd)
(sleep delay)))
MAKE-CMD
CL-USER> (setf hello (make-cmd 'hello 1))
#<FUNCTION :LAMBDA NIL (PRINT CMD) (SLEEP DELAY)>
CL-USER> (setf query (make-cmd 'query 2))
#<FUNCTION :LAMBDA NIL (PRINT CMD) (SLEEP DELAY)>
CL-USER> (setf end (make-cmd 'end 1))
#<FUNCTION :LAMBDA NIL (PRINT CMD) (SLEEP DELAY)>
CL-USER> (defun new-foo (cmds)
(dolist (cmd cmds) (funcall cmd)))
NEW-FOO
CL-USER> (time (new-foo (list hello query query end)))
HELLO
QUERY
QUERY
END
Real time: 6.005 sec.
Run time: 0.0 sec.
Space: 1352 Bytes
NIL
CL-USER>
Of course, writing a function like new-foo() isn't exactly working with the language orthogonally. What i'd really want to do is use a map instead.
Code:
CL-USER> (time (mapcar #'funcall (list hello query query end)))
HELLO
QUERY
QUERY
END
Real time: 6.006 sec.
Run time: 0.0 sec.
Space: 1376 Bytes
(NIL NIL NIL NIL)
CL-USER>
I think that is a pretty good example of composing functions. Of course, the make-cmd function that I wrote/used isn't very general. A true function builder would allow the user to pass in multiple functions at run-time. I was trying to keep things as simple as possible. One of the best uses of composition is called memoization. In a nutshell, if you have a very time consuming function that makes recursive calls, you can "memoize" repeated function calls. You pass your function to memoize which returns a new function that uses a hash table to store the result (the args are the key, the result the value). Repeated calls with the same key are retrieved from the hash table isntead of being computed. By using composition, you're able to write one memoize function that is able to work with any number of input functions (as opposed to some one off solution for a single function).
Code:
CL-USER> (defun memoize (fn)
(let ((cache (make-hash-table :test #'equal)))
#'(lambda (&rest args)
(multiple-value-bind (val win)
(gethash args cache)
(if win
val
(setf (gethash args cache) (apply fn args)))))))
MEMOIZE
CL-USER> (defun foo (n)
"Foo sleeps for n seconds then returns 1 + n."
(sleep n)
(1+ n))
FOO
CL-USER> (setf mem-foo (memoize #'foo))
#<FUNCTION :LAMBDA (&REST ARGS)
(MULTIPLE-VALUE-BIND (VAL WIN) (GETHASH ARGS CACHE)
(IF WIN VAL (SETF (GETHASH ARGS CACHE) (APPLY FN ARGS))))>
CL-USER> (time (mapcar mem-foo '(1 2 3 4))) ;;<-- calling the memoized version of foo with four times with 1 2 3 and 4 as input
Real time: 10.0 sec. ;;<-- the amount of time it took due to the sleep function
Run time: 0.0 sec.
Space: 372 Bytes
(2 3 4 5) ;;<-- the return values
CL-USER> (time (mapcar mem-foo '(1 2 3 4)))
Real time: 0.0 sec. ;;<-- the memoized values are returned here instead
Run time: 0.0 sec.
Space: 64 Bytes
(2 3 4 5)
CL-USER>