javascript| Script
Recently on the Internet to see an article, said JavaScript = C+lisp, so think about the problem, since JavaScript contains some of the lisp of the lineage, then use JavaScript to implement a Lisp-like AI script what will it look like?
As a "functional" language family, the LISt processing has conquered many researchers and enthusiasts with its simple and graceful style and concise and efficient structure since the birth date.
This ancient language and grammar is still used by many people and love, but also in the field of artificial intelligence plays a very large role.
I think that the flexibility of JavaScript plus the simplicity of Lisp should be able to create a very beautiful language, but what is the language like? I'm sure you would like to know, so let's look at this very appealing question together.
(Before you read the following, we recommend that you pour a cup of hot tea, sit down and calm down, take a deep breath, and concentrate on the spirit, because the following process will be fun and sometimes a lot of brain-wasting cells ... ^^)
Before going into the Lisp kingdom, let's start with some javascrip preparations ... Take a closer look at the following code
NIL = [];
Array.prototype.toEvalString = function ()
{
if (this.length <= 0) return "NIL";
var str = "";
for (var i = 0; i < this.length; i++)
{
if (This[i] instanceof Array)
STR + + "," + this[i].toevalstring ();
else str = "," + this[i];
}
Return "[" + str.slice (1) + "]";
};
(function () {
Lispscript = {
Run:run
};
function run (code)
{
If (Code instanceof Array)
{
var elements = new Array ();
for (var i = 0; i < code.length; i++)
{
Code[i] = run (code[i]); Recursive read down
if (Code[i] instanceof Function)//Parse expression
{
if (code[i].length <= 0)//parameterless function can omit [] directly called by function name
{
Code[i] = Code[i].call (null);
}
else if (i = = 0)//Call function with parameters [Funcall,args ...]
{
return code[i].apply (NULL, Code.slice (1));
}
}
}
return code;
}
return Element (code);
};
})();
function Assert (MSG, cond)
{
if (cond)
return true;
Else
{
Alert (msg);
throw new Error (msg);
}
};
function Element (ARG)
{
if (arg = = null)
return [];
else if (Arg instanceof Function && arg.length <= 0)
return arg.call (NULL);
Else
return arg;
};
__funlist = new Array ();
This is a simple paragraph. Dozens of lines of JavaScript code consists of three auxiliary functions, a principal object, a constant nil (which we know later to represent an empty table or false logic), and a stack that holds the name of the function.
The Lispscript static object forms the body of the Lispscript parser, which has only one run method, which parses the passed in Lispscript code in a downward recursive manner, the type of code-a reader who believes that careful readers have found The direct use is an array of JavaScript, a series of "[", "]" and "delimiter", "sequences".
With JavaScript's natural array features, our parser can be designed to be very concise-without splitting and parsing each token, so a short to less than 50 line of code is amazingly fulfilling the core of the entire LISPSCRIPT parser!
The function of three auxiliary functions is to provide parsing for function iterations (toevalstring), Detect sequence anomalies (Assert, not actually used in subsequent implementations), and parse instruction words (Element)
Next we define the expression. An expression or an atom [atom], which is an alphabetic sequence (such as Foo) or a table (list) consisting of 0 or more expressions, separated by commas, and placed in a pair of brackets. Here are some of the expressions:
(Note: An expression of the original Lisp syntax is separated by a space and placed in a pair of parentheses.) Because it's a JavaScript implementation, it's simpler with parentheses and commas.
Foo
[]
[Foo]
[Foo,bar]
[A,b,[c],d]
The last expression is a table of four elements, and the third element itself is a table consisting of one element.
In arithmetic the expression 1 + 1 draws a value of 2. The correct Lisp expression also has a value. If the expression e gets the value V, we say e returns v. Next we'll define several expressions and their return values.
If an expression is a table, we call the first element the operator, and the rest of the elements as arguments. We will define seven primitive (in the sense of axiom) operators: Quote,atom,eq,car,cdr,cons, and cond.
[Quote,x] returns x. We denoted [quote,x] as [_,x].
> [Quote,a]
A
> [_,a]
A
> [Quote,[a b c]]
[A,b,c]
Quote = _ = function (args)
{
if (Arguments.length < 1)
return [];
else if (arguments.length >= 1)
{
return arguments[0];
}
};
[Atom,x] Returns the Atom true if the value of X is an atom or an empty table, otherwise return []. In Lisp we routinely use atomic true to represent true, whereas empty tables represent false.
> [Atom,[_,a]]
True
> [Atom,[_,[a,b,c]]]
[]
> [atom,[_,[]]]
True
Atom = function (ARG)
{
var tmp = Lispscript.run (ARG); Evaluate the parameters first
if (!) ( TMP instanceof Array) | | Tmp.length <= 0)
return true;
Else
return [];
};
Now that we have an operator with an argument that requires a value, we can look at the role of quote. by referencing (quote) a table, we avoid it being evaluated. An unreferenced table passed as an argument to an operator like Atom will be treated as code:
> [Atom,[atom,[_,a]]]
True
Conversely, a referenced table is treated as a table, in this case, a table with two elements:
> [Atom,[_,[atom,[_,a]]]]
[]
This is consistent with the way we use quotes in English. Cambridge (Cambridge) is a town of 90000 people in Massachusetts. and "Cambridge" is a word consisting of 9 letters.
The reference may seem a little strange because very few other languages have similar concepts. It is closely related to the most distinctive features of Lisp: Code and data are composed of the same data structure, and we use the quote operator to differentiate them.
[Eq,x,y] returns T if the value of x and Y is the same atom or both are empty tables.
> [Eq,[_,a],[_,a]]
True
> [Eq,[_,a],[_,b]]
[]
> [eq,[_,[]],[_,[]]]
True
equal = EQ = function (arg1, arg2)
{
var tmp1 = Lispscript.run (arg1);
var tmp2 = Lispscript.run (arg2); Evaluate the parameters first
if (!) ( TMP1 instanceof Array) &&! (TMP2 instanceof Array) &&
tmp1.tostring () = = tmp2.tostring () | |
(tmp1 instanceof function) && (tmp2 instanceof function) && tmp1.tostring () = = tmp2.tostring () | |
(tmp1 instanceof Array) && (tmp2 instanceof Array) && (tmp1.length = 0) && (tmp2.length = 0))
return true;
Else
return [];
};
[Car,x] expects the value of X to be a table and returns the first element of X.
> [Car,[_,[a b c]]]
A
Car = function (ARG)
{
var tmp = Lispscript.run (ARG); Evaluate the parameters first
if (tmp instanceof Array && tmp.length > 0)
return tmp[0];
Else
return [];
};
[Cdr,x] expects the value of X to be a table and to return all elements after the first element of X.
> [Cdr,[_,[a b c]]]
[B,c]
CDR = function (ARG)
{
var tmp = Lispscript.run (ARG); Evaluate the parameters first
if (tmp instanceof Array && tmp.length > 0)
return Tmp.slice (1);
Else
return [];
};
[Cons,x,y] expects the value of Y to be a table and returns a new table whose first element is the value of x, followed by the elements of the value of Y.
> [Cons,[_,a],[_,[b,c]]]
[A,b,c]
> [cons,[_,a],[cons,[_,b],[cons,[_,c],[_,[]]]]
[A,b,c]
> [Car,[cons,[_,a],[_,[b c]]]]
A
> [Cdr,[cons,[_,a],[_,[b,c]]]]
[B,c]
Cons = function (Arg1, arg2)
{
var tmp1 = Lispscript.run (arg1);
var tmp2 = Lispscript.run (arg2); Evaluate the parameters first
if (tmp2 instanceof Array)
{
var list = new Array ();
List.push (TMP1);
Return List.concat (TMP2);
}
Else
return [];
};
[Cond [...] [...]] The evaluation rules are as follows. The P expression is evaluated sequentially until there is a return of T. If you can find such a P expression, the value of the corresponding e-expression is the return value of the entire cond expression.
> [Cond,[[eq,[_,a],[_,b]],[_,first]],
[, [Atom,[_,a]], [_,second]]]
Second
Cond = function (args)
{
for (var i = 0; i < arguments.length; i++)
{
if (Arguments[i] instanceof Array)
{
var cond = Lispscript.run (arguments[i][0]); Evaluate the parameters first
alert (cond);
if (cond = = True && arguments[i][1]!= null)
Return Lispscript.run (arguments[i][1]);
}
}
return [];
};
When an expression begins with five of the seven original operators, its arguments always require a value of. 2 We call such an operator a function.
Then we define a notation to describe the function. The function is represented as [lambda, [...], E), where ... is an atom (called an argument) and E is an expression. If the first element form of an expression
[[Lambda,[...],e],...]
is called a function call. Its value is computed as follows. Each expression is evaluated first, and then E is evaluated. In the evaluation of E, each value appearing in E is the corresponding value in the last function call.
> [[lambda,[' X '],[cons, ' x ', [_,[b]]]],[_,a]]
[A,b]
> [[lambda,[' x ', ' Y '],[cons, ' x ', [Cdr, ' Y ']]],[_,z],[_,[a,b,c]]]
[Z,b,c]
Lambda = function (args, code)
{
If (Code instanceof Array)
{
var fun = new Function (args,
"For" (var i = 0; i < arguments.length; i++) Arguments[i] = Lispscript.run (arguments[i]); return Lispscript.run ("+code.toevalstring () +");
var globalfuncname = __funlist.pop ();
Fun._funname = Globalfuncname;
if (globalfuncname!= null)
Self[globalfuncname] = fun;
return fun;
}
return [];
};
If the first element f of an expression is an atom and F is not the original operator
[F ...]
and the value of f is a function [lambda,[...]], the value of the expression above is
[[Lambda,[...],e],...]
The value. In other words, parameters can be used not only as arguments but also as operators in an expression:
> [[lambda,[f],[f,[_,[b,c]]],[_,[lambda,[x],[cons,[_,a],x]]]
[A,b,c]
There is another function notation that allows the function to refer to itself, so that we can easily define recursive functions. mark
[Label,f,[lambda,[...],e]]
Represents a function like [lambda,[...],e], plus the attribute: Any F that appears in E is evaluated for this label expression, as if f is an argument to this function.
Suppose we want to define a function [Subst,x,y,z], which takes expression x, Atom Y, and table z as arguments, and returns a table like Z, but the y (at any nesting level) in Z is replaced by X.
> [subst,[_,m],[_,b],[_,[a,b,[a,b,c],d]]]
[A,m,[a,m,c],d]
We can represent this function
[Label,subst,[lambda,[x,y,z],
[Cond,[[atom,z],
[Cond,[[eq,z,y],x],
TRUE,Z]]],
[True,[cons,[subst,x,y,[car,z]],
[Subst,x,y,[cdr,z]]]]]
label = function (Funname, fundef)
{
__funlist.push (Funname);
Return Lispscript.run (FUNDEF);
};
We denoted F=[label,f,[lambda,[...],e]]
[Defun,f,[...],e]
Defun = function (funname, args, code)
{
__funlist.push (Funname);
If (Code instanceof Array)
{
var fun = new Function (args,
"For" (var i = 0; i < arguments.length; i++) Arguments[i] = Lispscript.run (arguments[i]); return Lispscript.run ("+code.toevalstring () +");
var globalfuncname = __funlist.pop ();
Fun._funname = Globalfuncname;
if (globalfuncname!= null)
Self[globalfuncname] = fun;
return fun;
}
return [];
};
So
[Defun,subst,[x,y,z],
[Cond,[[atom,z],
[Cond,[[eq,z,y],x],
[True,z]]],
[True,[cons,[subst,x,y,[car,z]],
[Subst,x,y,[cdr,z]]]]]
Incidentally, here we see how to write the default clause for a COND expression. The first element is a clause of ' t that will always succeed. So
[Cond,[x,y],[[_,true],z]]
Equivalent to what we wrote in some languages.
If X then y else Z
For function calls, it has the following structure: [Funname,[_,args]]
Where Funname is the function name, [_,args] is the specified parameter reference list args
Note [Funname,args] is also legal, but there is a difference from [Funname,[_,args]], for the former, the instruction computes the value of the args before being invoked, substituting the computed value as a parameter list into the function calculation (expecting the args result to be a list). The args parameter list of the latter is computed when the function instruction is invoked
So far, we're happy to see that Lispscript has been able to expand without relying on JavaScript.
Now we can define some new functions directly with Lispscript:
function: [ISNULL,X] Tests whether its argument is an empty table.
Lispscript.run (
[Defun, ' isNull ', [' X '],
[EQ, ' x ', [_,nil]]]
);
> [Isnull,[_,a]]
[]
> [IsNull. [_,[]]]
T
function: [and,x,y] returns T if both of its arguments are T, otherwise return [].
Lispscript.run (
[Defun, ' and ', [' X ', ' Y '],
[cond,[' x ', [cond,[' Y ', True],[true,nil]],
[True,nil]]]]
);
> [And,[atom,[_,a]],[eq,[_,a],[_,a]]]
T
> [and,[atom,[_,a]],[eq,[_,a],[_,b]]]
[]
function: [not,x] returns t if its argument returns [], returns [] if its argument returns T.
Lispscript.run (
[Defun, ' not ', [' X '],
[cond,[' x ', NIL],
[True,true]]]
);
> [Not,[eq,[_,a],[_,a]]]
[]
> [not,[eq,[_,a],[_,b]]]
T
function: [Append,x,y] takes two tables and returns their links.
Lispscript.run (
[Defun, ' Append ', [' X ', ' Y '],
[Cond,[[isnull, ' X '], ' y '],
[True,[cons,[car, ' x '],[' append ', [Cdr, ' X '], ' y ']]]]
);
> [append,[_,[a,b]],[_,[c,d]]]
[A,b,c,d]
> [append,[], [_,[c,d]]]
[C,d]
function: [Pair,x,y] takes two identical-length tables, returns a table consisting of two-element tables, and the double-element table is the X,y element pair of the corresponding position.
Lispscript.run (
[Defun, ' pair ', [' X ', ' Y '],
[Cond,
[[And,[isnull, ' x '],[isnull, ' y ']],nil],
[[And,[not,[atom, ' x ']],[not,[atom, ' y ']]],
[Append,[[[car, ' x '],[car, ' y ']]],[' pair ', [Cdr, ' X '],[cdr, ' y ']]]
]]]
);
> [Pair,[_,[x,y,z]],[_,[a,b,c]]]
[[X,a],[y,b],[z,c]]
[Assoc,x,y] takes the atomic x and the table y returned by the shape like the pair function, returns the second element of the first table in Y that matches the following criteria: its first element is x.
Lispscript.run (
[Defun, ' Assoc ', [' X ', ' Y '],
[Cond,[[eq,[car,[car, ' Y ']], ' x '],[car,[cdr,[car, ' y ']],
[[IsNull, ' Y '],nil],[true,[' assoc ', ' x ', [Cdr, ' Y ']]]]
);
> [assoc,[_,x],[_,[[x,a],[y,b]]]]
A
> [assoc,[_,x],[_,[[x,new],[x,a],[y,b]]]]
New
[Ret,e] Returns the result of the expression evaluation
Lispscript.run (
[Defun, ' ret ', [' e '],[car,[' e ']]]
);
[Str,e] Returns a reference to the result of an expression evaluation
Lispscript.run (
[Defun, ' str ', [' E '],[_,[_, ' e ']]]
);
Let's take a look at why you define a RET function:
I think through the previous explanations and practical applications, you have already understood the importance of the reference (quote) and it is easy to prove that: [[_,e]] = [E]
The problem now is that we have to define a reference to the inverse function f, so [f,[_,e]] = E
And apparently RET is just such a function
[Map,x,y] expects X to be an atom, and Y is a table, and if [Assoc,x,y] Non-null returns the value of [assoc,x,y], it returns x
Lispscript.run (
[Defun, ' map ', [' X ', ' Y '],
[Cond,[[isnull,[assoc, ' x ', ' Y ']], ' x '],[true,[assoc, ' x ', ' Y ']]]
);
[Maplist,x,y] expects X and Y to be tables, and returns a table consisting of the results of each element t of x seeking [map,t,y]
Lispscript.run (
[Defun, ' maplist ', [' X ', ' Y '],
[Cond,
[[Atom,[_, ' x ']],[map, ' x ', ' Y ']],
[true,[cons,[' maplist ', [Car,[_, ' X ']], ' y '],[' maplist ', [Cdr,[_, ' X ']], ' y ']]
]
]
);
So we can define functions to join tables, replace expressions, and so on. Maybe it's a graceful notation, so what's the next step? Now the surprise is here. We can write a function as an interpreter of our language: This function takes any Lisp expression as an argument and returns its value. As shown below:
Lispscript.run (
[Defun, ' _eval ', [' e ', ' a '],
[Ret,[maplist,[_, ' e '], ' a ']]
]
);
The simplicity of the _eval is perhaps beyond our original expectations, so we get a complete parser of LISPSCRIP implementations!
Let's go back and think about what this means. We've got a very graceful computational model here. Using only quote,atom,eq,car,cdr,cons, and cond, we define the function _eval. It actually implements our language, with which we can define and/or dynamically generate any additional functions and grammars that we want (which is more important).
Other (slightly more complex) extensions:
Here we define the assignment operation of the variable [setq,paraname,paravalue]
Lispscript.run (
[Defun, ' setq ', [' Para ', ' Val '],
[Ret,[defun, ' para ', [],[_eval, ' Val ']]]
);
Add logical operator Or,[or,x,y] returns t if its argument has one for T, otherwise returns []
Lispscript.run (
[Defun, ' or ', [' X ', ' Y '],
[Not,[and,[not, ' x '],[not, ' y ']]]]
);
Increase cycle control foreach,[foreach,v,[list],[expr]]
The Foreach expectation list is a table in which each atom in the table is computed as a parameter of expr, returning a table of calculated results.
Lispscript.run (
[Defun, ' foreach ', [' V ', ' list ', ' expr '],
[Cond,
[[IsNull, ' list '],[]],
[True,[cons,[_eval,[_, ' expr '],[[' V ', [car, ' list ']]]],[' foreach ', ' V ', [CDR, ' list '],[_, ' expr ']]]
]
]
);
Add bulk assignment Operation Let,[let,[[a1,v1],[a2,v2]]
Lispscript.run (
[Defun, ' Let ', [' paralist '],
[foreach, "' V '", ' paralist ', [_,[setq,[car, ' V ' "],[car,[cdr, ' ' V '"]]]]
]
);
Summarize
Now it's time to look back and see what we have done and what it means to do so.
First we implemented a simple recursive lexical analyzer with JavaScript, which can simply handle each atom of a nested array, plus several auxiliary functions (toevalstring (), Assert (), Element (), and a stack that holds the name of the function ... In short, we have only dozens of lines of code to implement a new "functional" language?? The full kernel of the lispscript.
Then we define 7 primitive operations, which are quote,atom,eq,car,cdr,cons and cond, respectively.
Then (relatively complex), we define three tags that describe and invoke functions, which are lambda, label, and Defun, so we succeeded in implementing the core environment of the Lispscript language with less than hundred lines of code.
Then (the next section is completely independent of JavaScript) we define some new functions with 7 primitive operators and function definition tags defun: isnull,and,not,append,pair,assoc,ret and Str
Then we were pleasantly surprised to find that we could define our "parser" with just one line of lispscript instructions?? _eval function
Finally, we define some slightly complex functions, including: Or,setq,foreach and let, some of the new functions to our new language to define variables and the ability to deal with the cycle, coupled with some of the previous implementation of the functions, a relatively perfect foundation environment is built.
Written at the end: Lispscript and Lisp
In fact, we follow [ref Paul Graham.] The wonderful description of Lispscript is a lisp (or Lisp-style functional language) that, despite its rudimentary functionality, is true to Lisp's basic ideas and the basic features of Lisp. Because of the characteristics of the JavaScript array grammar, I replaced the () in [Ref. Paul Graham] with a comma instead of a space as a separator. Unlike the [ref Paul Graham] article and the current standard (or relative standard) Lisp, I deliberately weaken the lispscript syntax based on JavaScript's flexible features, which makes lispscript more flexible, is also more convenient to implement, yet the cost is a small portion of maintainability and security.
Finally, there are many things that need to be perfected in lispscript, for example, it is most obvious that it basically does not have the basic numerical computing power (relatively, the symbolic operation ability has been relatively perfect), in addition to the atomic operating parameters of the validity of the test, side effects, continuous execution (it has to be with side effects of the use), Dynamic visual fields, complex data structure support, and annotation grammars (this is very important!) are also lacking, but these features "can be surprisingly remedied with very little extra code."
Thanks to John McCarthy, this genius has shown us decades ago the "ultimate Beauty" that no one in the field of programming has ever been able to transcend, he published an extraordinary paper in 1960, and his contribution to programming in this paper is like Euclid's contribution to geometry. 1 He showed us, How to construct a complete programming language on the basis of only a few simple operators and a notation that represents a function. McCarthy calls this language Lisp, which means list processing, because one of his main ideas is to represent code and data in a simple data structure table (list).
Thanks to Paul Greham, he presented Lisp's roots and substance to us in plain language, enabling us to be fortunate enough to experience Lisp's "extraordinary beauty" 0 of miles away.
If you understand John McCarthy's eval, you're not just understanding a stage in the history of programming languages. These ideas are still the core of Lisp's semantics. So in a sense, learning John McCarthy's original book shows us what Lisp really is. Lisp is not so much about McCarthy's design as his discovery. It is not born to be a language used for artificial intelligence, rapid prototyping, or the same level of task. It is the result of your attempt at axiomatic calculation (one).
Over time, intermediate languages, the languages that are used by middle-tier programmers, are consistently approaching Lisp. So by understanding eval you're getting a sense of what the mainstream computing model will look like in the future.
References
The Roots of Lisp Paul Graham. Draft, January 18, 2002.
LISt Primer Colin Allen & Maneesh dhagat.tue Feb 6, 2001. (http://mypage.iu.edu/~colallen/lp/lp.html)