Skip to content

Instantly share code, notes, and snippets.

@LuckyKoala
Last active April 29, 2024 04:07
Show Gist options
  • Save LuckyKoala/5ddf8fddf50b2640166c1b75e2ba5934 to your computer and use it in GitHub Desktop.
Save LuckyKoala/5ddf8fddf50b2640166c1b75e2ba5934 to your computer and use it in GitHub Desktop.
Javascript implementation for SICP Chapter 2.
sicp = (function() {
//-----Base implementation------
//Primitive procedure
let cons = (x,y) => [x,y];
let car = (pair) => pair[0];
let cdr = (pair) => pair[1];
//Nil definition
let nil = [];
let isNil = (obj) => obj==null || (Array.isArray(obj) && obj.length==0);
//Helper procedure to build, extract, compare and print list
let cadr = (pair) => car(cdr(pair));
let list = (...args) => {
if(args.length > 1) return cons(args[0], list(...args.slice(1)));
else if(args.length == 0) return nil;
else return cons(args[0], nil);
};
let isEqual = (seq1, seq2) => JSON.stringify(seq1)===JSON.stringify(seq2);
let print = (seq) => JSON.stringify(seq);
//List operations
let listRef = (seq, n) => {
if(n==0) return car(seq);
else return listRef(cdr(seq), n-1);
};
let length = (seq) => {
let lengthIter = (s, count) => {
if(isNil(s)) return count;
else return lengthIter(cdr(s), count+1);
};
return lengthIter(seq, 0);
};
let append = (list1, list2) => {
if(isNil(list1)) return list2;
else return cons(car(list1), append(cdr(list1), list2));
};
//------Test------
(function() {
//Test for usage of primitive procedure which is defined before
let test = cons(1,2);
console.log("can extract 1 and 2: " + (car(test)==1 && cdr(test)==2));
let test1 = cons(1, cons(2,cons(3,4)));
console.log("can extract 1,2,3 and 4 from more complicate pair: "
+ (car(test1)==1 && car(cdr(test1))==2 && car(cdr(cdr(test1)))==3
&& cdr(cdr(cdr(test1)))==4));
//Test for usage of helper procedure
console.log("list() === nil: " + (list()===nil));
test = cons(1, cons(2,cons(3,cons(4,nil))));
test1 = list(1, 2, 3, 4);
console.log("list EQUAL recursive cons: " + isEqual(test, test1));
//Test for usage of list operations
test = list(0,1,2,3,4,5);
refResult = true;
for(let i=0; i<=5; i++) {
refResult &= listRef(test, i)==i;
}
console.log("listRef can work: " + refResult);
console.log("length can work: " + (length(test)==6));
test1 = list(6);
test2 = list(0,1,2,3,4,5,6);
console.log("append can work: " + isEqual(append(test,test1), test2));
})();
//------Solutions of ex2.17------
let lastPair = (seq) => {
if(isNil(seq)) return nil;
let rest = cdr(seq);
if(isNil(rest) && isNil(car(rest))) return car(seq);
else return lastPair(rest);
};
//------Solutions of ex2.18------
let reverse = (seq) => {
let reverseIter = (ret, s) => {
if(isNil(s)) return ret;
let first = car(s);
let rest = cdr(s);
return reverseIter(cons(first,ret), rest);
};
return reverseIter(nil, seq);
};
//------Tree------
let isPair = (seq) => Array.isArray(seq) && seq.length==2;
let countLeaves = (seq) => {
if(isNil(seq)) return 0;
else if(!isPair(seq)) return 1;
else return countLeaves(car(seq)) + countLeaves(cdr(seq));
};
//------Test------
(function() {
let x = cons(list(1,2),list(3,4));
let xx = list(x,x);
console.log("countLeaves can work: " + (length(x)==3 && countLeaves(x)==4
&& length(xx)==2 && countLeaves(xx)==8));
})();
//------Higher-order procedure------
let map = (proc, seq) => {
if(isNil(seq)) return nil;
else return cons(proc(car(seq)), map(proc, cdr(seq)));
};
let forEach = (proc, seq) => {
if(isNil(seq)) return;
else {
proc(car(seq));
forEach(proc, cdr(seq));
}
};
let filter = (predicate, seq) => {
if(isNil(seq)) return nil;
else if(predicate(car(seq))) return cons(car(seq), filter(predicate, cdr(seq)));
else return filter(predicate, cdr(seq));
};
let accumulate = (op, initial, seq) => {
if(isNil(seq)) return initial;
else return op(car(seq), accumulate(op, initial, cdr(seq)));
};
let enumerateInterval = (low, high) => {
if(low>high) return nil;
else return cons(low, enumerateInterval(low+1, high));
};
let enumerateTree = (tree) => {
if(isNil(tree)) return nil;
else if(!isPair(tree)) return list(tree);
else return append(enumerateTree(car(tree)), enumerateTree(cdr(tree)));
};
//------Test------
(function() {
let isOdd = (n) => n%2==1;
let square = (n) => n*n;
let plus = (a,b) => a+b;
let sumOddSquares = (tree) => accumulate(plus, 0, map(square, filter(isOdd, enumerateTree(tree))));
let result = sumOddSquares(list(1, 2, 4, 3, 5));
console.log("High-order procedures can work: " + (result==35));
})();
//------Solutions of ex2.33------
(function () {
let map = (proc, seq) => accumulate((operand, ret) => cons(proc(operand), ret), nil, seq);
let test = list(1,2,3);
let correctResult = list(1,4,9);
console.log("Map as accumulations can work: " + isEqual(map((x) => x*x, test), correctResult));
let append = (seq1, seq2) => accumulate(cons, seq2, seq1);
let test1 = list(4,5,6);
correctResult = list(1,2,3,4,5,6);
console.log("Append as accumulations can work: " + isEqual(append(test, test1), correctResult));
let length = (seq) => accumulate((opperand, ret) => ret+1, 0, seq);
console.log("Length as accumulations can work: " + (length(test) == 3));
})();
//Export object in case not to pollute global enviroment
return {
//Base
cons: cons,
car: car,
cdr: cdr,
nil: nil,
isNil: isNil,
//List
cadr: cadr,
list: list,
isEqual: isEqual,
print: print,
listRef: listRef,
length: length,
append: append,
//Other
lastPair: lastPair,
reverse: reverse,
//Tree
isPair: isPair,
countLeaves: countLeaves,
//Sequence - Signals Flow
map: map,
forEach: forEach,
filter: filter,
accumulate: accumulate,
enumerateInterval: enumerateInterval,
enumerateTree: enumerateTree,
};
/*
//flatten(1);
let flat = (seq) => {
let flatIter = (ret, rest) => {
if(isNil(rest)) return ret;
else return flatIter(ret.concat(car(rest)), cdr(rest));
}
return flatIter([], seq);
};
*/
})();
//------Usage------
/*
with(sicp) {
let seq = list(1,2,3,4);
print(seq);
...Code here can use enviroment which was exported to object sicp.
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment