My obsession with things schemish and lispish and having to use php on a daily basis leads to the usual comparison and how would I do this in ... style questions. So here comes another go at how would I program lisp style in php. How much is possible? What is possible? Mind you not everything is really useful or the best way to do it. It is just an intellectual excersise, a study on using some of the php'f features to program in not a typical php (imperative) style.
the compulsory conses, car, cdr in php
Well for the benefit of non-lispers I will use head and tail instead of car and cdr respectively.
function head($list) {
return array_shift($list);
}
The natural equivalent native structure to a lisp list would be a php array. Ok, nearly equivalent. I don't want to go into too much detail at this point.
function tail($list) {
array_shift($list);
return $list;
}
function cons() {
return func_get_args();
}
These two are driven by the same assumptions as before. You can see that there is a nice symmetry between them. In cons the argument list is used as a LISP or Scheme list would be used.
lisp-like quotes in php
function quote() {
return func_get_args();
}
That was easy. It does not do anything to really quote the arguments of the function, so you need to manually quote everything you need. A typical application would be something like:
$x = quote('printer', 'two', 'three' );
unquote( $x );
Well in order to make better sense here's the reverse unquote
function unquote( $list ) {
$calleable = array_shift($list);
return call_user_func_array($calleable, $list);
}
Apply
function apply() {
$args = func_get_args();
$func = shift($args);
return call_user_func_array( $func, $args);
}
Apply is an alias to the native call_user_func_array(). Is there an need to explain more?
a scheme like begin
function begin() {
$args = func_get_args();
for($k = func_num_args(), $i=0; $i<$k; $i++) {
$out = is_callable( $args[$i] )? call_user_func($args[$i]): $args[$i];
}
return $out;
}
This will allow for sequencing functions
begin( some_function_application, some_other_function)
Note that all the arguments to begin are either thunks, functions with no arguments, or self-evaluating, non-calleables.
php thunks in trunks
This time, it will be a class based implementation. It doesn't need to be, but let's vary the techniques.
class thunk {
var $args;
var $name;
function thunk() {
$args = func_get_args();
if($args[0][0]=='make') {
$args= $args[0];
array_shift($args);
}
$name = array_shift($args);
$this->args = $args;
$this->name = $name;
}
function ammend() {
$this->args = array_merge($this->args, func_get_args());
}
function call() {
$args = $this->args;
$name = $this->name;
if( func_num_args() ) {
$args = array_merge($this->args, func_get_args());
}
return call_user_func_array($name,$args);
}
}
And thunk's little helpers:
function is_thunk( $obj ) {
return is_a($obj,'thunk');
}
function make_thunk() {
$args = func_get_args();
if( is_thunk( $args[0][0] ) ) {
$obj = array_shift($args);
$obj =& $obj[0];
$obj->ammend($args);
}
else {
array_unshift($args,'make');
$obj = new thunk( $args );
}
return array($obj, 'call');
}
There is a lot of sugar here. The implementation could have been far leaner, but I wanted to be able to mimic partial evaluation, hence this code. It is not really a thunk - remmember, a thunk is any valid php callback which doesn't require arguments.
A lightweight lazy (delayed) evaluation
Using the above definitions:
function delay() {
$args = func_get_args();
return make_thunk($args);
}
This is an implemention of a lazy function. It returns a promise, which when forced will execute the delayed function.
function force( $thunk ) {
return call_user_func( $thunk );
}
An example force implementation, which essentially is an alias for the native call_user_func.
One of the few essentials left is lambdas. I'll leave the implementation for the eager reader for the time being. The only reall catch for implementing lambda() is scoping. Otherwise the code will look very close to the thunk one. In order to simplify the functions in this post I avoid scoping altogether.
Use defined functions?
Wouldn't it be more appropriate to use the LISPish functions already defined in the definitions of newer LISPish functions?
Example:
$calleable = head($list);instead of
$calleable = array_shift($list);Yes
Yes.
By the way, these days there are more array functions to choose from, as well as closures, and SPL. Implementation would probably differ. The delay/force duo is still very useful in implementing call by name to reduce running times by avoiding unnecessary evaluation, but should be used with care - watch the stack.
Arrays as lists.
I found this page randomly while bored at work and looking at both lisp implementions i php and a lisp compiling to php. Anyways, nice with a freetard who also writes lispish php i though my lispish php i wrote at work was mostly a subject of hatred among the php-developers but now it's nice to see kindred spirits in this strange world of lisp that i'm tortured by at work and even nicer that it is another freetard.
Anyways, i have a strange way of tending to return arrays and use them as arguements, which have made me start to get annoyed by the fact that i always have to write array in front of every array definition i ever write.
Ofcourse thus i have constructed this silly function that most people find uneccesarry but make my life better and my code easier to read (once you know what l stands for)
function l ()
{
$l = array ();
for ($i = 0; $i < func_num_args (); $i++)
{
$l[] = func_get_arg ($i);
}
return $l;
}
do_the_stuff (array (1, 2, 3));
is now
do_the_stuff (l(1, 2, 3));
still annoying though.
And..
do_stuff (l(l(1, 2, 3), l(4, 5, 6)));
for instance is a bit confusing imho.
I tend to indent it better for easier readability
do_stuff (l(
l(1, 2, 3),
l(4, 5, 6)));
if you just learn to read l( as ( it sort of make sense in a lispish way.
Otherwise i always have a space between function_name and (.
And yes rewriting array () is silly, but i use it so often and get so damn irritated by it so i just had to. to bad you can't get away without actually having a function at all. (Some of my colleagues claim that some of my code is really hard to read, i really don't understand why)
Sometimes to avoid typing
Sometimes to avoid typing array much I do something like:
function l() { return func_get_args(); }
Slightly faster, and more readable. Still, a bit over the top.