|
|
Some zkl examples
Some random chunks of zkl code. Check out the illustrated
examples (PDF) for more. Or the zkl page at Rosetta
Code for hundreds more.
Factorial a bunch of ways:
fcn factTrad(n) { // "Classic"
if (n == 0) return(1);
return(n*self.fcn(n - 1));
}
fcn factTail(n,N=1) { // Tail recursive, compiles to a loop
if (n == 0) return(N);
return(self.fcn(n →
1,n * N));
}
fcn factShort(n) // No [explicit] if/else
{ n and n*self.fcn(n - 1) or 1 }
[2..n].reduce('*,1) // Another way of
writing a loop
fcn fact(n)
{(1).reduce(n,fcn(N,n){N*n},1)}
These are interesting because they show the
differences in stack usage between "regular" recursion and tail
recursion. The zkl math stack is small so large expressions can
blow that stack (and "n * recursion" effectively creates a looong
expression). But tail recursion turns the recursive call into a
jump and the stack isn't used. Using BigNums:
BigNum := Import("zklBigNum");
factTrad(BigNum(100)) → stack overflow
factTail(BigNum(100)) →
933262154439441526816992388562667004907159682643816214685929638952
17599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
(The compiler has since been tweaked to not use the stack in this
case).
While on the topic of tail recursion:
fcn fibTail(n,a=0,b=1){ if (n<=0) return(a) else
return(self.fcn(n-1,b,a+b)) }
T.build(20,1,fibTail) →
L(0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181)
(This last statement builds a [read only] list by applying an
index to a function)
Coroutines (See coroutines in
the manual (Utils.Generator) ):
fcn producer(resource)
{ foreach x in (resource) { vm.yield(x) }}
fcn consumer(producer) {
result := List();
foreach x in (producer) { result.append(x); }
result;
}
consumer(Utils.Generator(producer,"This is a test")); →
L("T","h","i","s"," ","i","s"," ","a"," ","t","e","s","t")
consumer(Utils.Generator(producer,File("helloWorld.txt")));
→ L("Hello World\n") The consumer could also
be written as:
fcn consumer(producer) { producer.walker().walk(); }
Generators are closures created with fibers.
Parse a time string ("hh:mm:ss") to
three integers:
fcn parseTime(time) {
reg hh=0,mm=0,ss=0;
try { hh,mm,ss = time.split(":").apply(fcn(x) { (x or
0).toInt() });
} catch(IndexError) {}
if (hh < 0 or hh > 23 or
mm < 0 or mm > 59 or ss < 0 or ss > 59)
throw(Exception.ValueError("Time: Invalid time:
%s".fmt(time)));
return(hh,mm,ss);
}
parseTime("1:2:3"); → L(1,2,3)
parseTime(":2:"); → L(0,2,0)
parseTime("::3"); → L(0,0,3)
parseTime("foo"); → ValueError :
"foo".toInt(10) contains an invalid character: f
Convert a decimal encoded string
("122,107,108") to a character string ("zkl"):
"122,107,108".split(",").apply("toInt")
.apply("toChar").concat("")
And the inverse: "zkl".split("").apply("toAsc").concat(",")
if (try { f(); True; } catch {False})
println("It ran"); else println("boom");
If f is fcn f { 1 } then the above prints "It
ran".
If f is fcn f { 1/0 } then "boom" is printed.
Time.Date.prettyDay()
→
"Saturday, the 11th of October 2008"
/* from Tamminen Eero
* Format weekday, month and year
* Day suffix cases:
* 1* : 11th, 12th, ... 19th
* *0 : 10th, 20th, 30th
* *1 : 1st, 21st, 31st
* *2 : 2nd, 22nd
* *3 : 3rd, 23rd
* Every other day: "th" : 4th, 5th, ... 9th, 24th,
* ... 29th
*/
fcn prettyDay(year=Void, month=1, day=1){
if (not year) year,month,day = Clock_.localTime;
return("%s, the %s%s of %s %s".fmt(
dayName(weekDay(year,month,day)), day,
if (1 == (day / 10)) "th" // 1*
else // *0, *1, *2, *3
try { L("th","st","nd","rd")[day % 10] }
catch { "th" } // all other days
,monthName(month),year));
}
Threads. Blink a LED every second (for
a tenth of a second), for 10 seconds, running in a OS thread:
Thread.HeartBeat(
fcn { ledOn(); Atomic.sleep(0.1); ledOff(); },
1,10).go();
Ruby:
# Ruby knows what you mean, even if you want to do math
on an entire Array
cities = %w[ London Oslo Paris Amsterdam Berlin ]
visited = %w[Berlin Oslo]
puts "I still need " + "to visit the " + "following
cities:",
cities - visited
zkl:
# Ruby example in zkl
var cities =
L("London","Oslo","Paris","Amsterdam","Berlin");
var visited = L("Berlin", "Oslo");
println("I still need ","to visit the ","following cities:
",
cities.copy().removeEach(visited) .concat(","));
or cities.filter(fcn(x){(not
visited.holds(x))})
→
I still need to visit the following cities:
London,Paris,Amsterdam
The zkl example is more verbose for the following reasons:
- Subtraction is destructive, so a copy of
cities is needed.
- Put a "," between each item instead of
printing L("London","Paris","Amsterdam")
- Or use the filter method, which, in this
example, creates a new list containing items not in visited.
Yeah, Ruby sure is easier in this example.
On Windows, I've been continually annoyed that
I can't use "find . -name '*.zkl' | xargs fgrep '.glob'".
Here is a script I wrote to do basically that. With this script,
the "find" would be "zkl zgrep -R. .glob *.zkl"
Attributes(script);
include(zkl.h.zkl);
var recurse, pattern, fileSpec;
argh := Utils.Argh(
ROList("+R","R","Recurse into subdirectories, starting at
<arg>",
fcn(arg) { recurse = arg }));
argh.parse(vm.arglist);
try { pattern,fileSpec = argh.loners; }
catch{
argh.usage(
"zgrep: A sorta combined find ... | xargs fgrep",
"zgrep [options] <pattern> fileSpec",
"zgrep [options] <pattern> -R<dir> fileSpec",
"eg zgrep -R. .glob *.zkl, zgrep .glob Src/*.zkl, zgrep
.glob [ST]*/*.zkl",
);
returnClass(Void);
}
fcn grep(fileName)
{ if (Void != File(fileName,"rb").read().find(pattern))
println(fileName); }
fcn grepAllTheWayDown {
done := Atomic.Bool();
pipe := Thread.Pipe();
fcn(pipe,done){ // running in a new thread
pipe.pump(0,grep); // grep(name) for all matching
files
done.set();
}.launch(pipe,done);
File.globular(recurse,fileSpec,True,FILE.GLOB.NO_DIRS,pipe);
done.wait(); // stall mother thread, don't exit to shell
'til done
}
if (recurse) grepAllTheWayDown();
else File.glob(fileSpec,FILE.GLOB.NO_DIRS).apply2(grep);
Just like on Unix, this uses two threads in a
pipe line, one thread to find the file name matches and another
thread to search the files. Garbage collection takes care of the
grep funcrtion not closing the files it opens.
Lua:
c = coroutine.create(function ()
print(1)
coroutine.yield()
print(2)
end)
coroutine.resume(c) →
1
coroutine.resume(c) →
2
zkl:
c := vm.createFiber(fcn { println(1); vm.yield(vm);
println(2); }) →
1
c.resume() →
2
Convert an integer to a commaized
string; nice, concise and recursive. How would you
write this using tail recursion? I don't know. In C? I do know
and it took over 15 lines (using one buffer and no recursion).
fcn commaize(n) { // int only, -1234567 -->
"-1,234,567"
if (n.abs() < 1000) n.toString();
else "%s,%03d".fmt(self.fcn(n/1000),n.abs() % 1000);
}
A Queens N puzzle solver:
fcn queensN(N=8,row=1,queens=T){
qs := [1..N].filter(isSafe.fpM("101",row,queens))
.apply(fcn(c,r,qs){qs+T(r,c)},row,queens);
if (row == N) return(qs);
return(qs.apply(self.fcn.fp(N,row+1)).flatten());
}
fcn isSafe(a,b,qs) { ( not qs.filter1(isAttacked,a,b) ) }
fcn isAttacked(q,a,b) { x,y:=q; (x==a or y==b or x+y==a+b or
x-y==a-b) }
queens := queensN(6);
println(queens.len()," solution(s): ");
queens.apply2(Console.println); 4 solution(s):
L(L(1,2),L(2,4),L(3,6),L(4,1),L(5,3),L(6,5))
L(L(1,3),L(2,6),L(3,2),L(4,5),L(5,1),L(6,4))
L(L(1,4),L(2,1),L(3,5),L(4,2),L(5,6),L(6,3))
L(L(1,5),L(2,3),L(3,1),L(4,6),L(5,4),L(6,2))
An infinite Fibonacci sequence:
var fib = fcn(ab){a,b:=ab; ab.del(0)+(a+b);
a}.fp(L(0,1));
do(15){print(fib(),",")} →
0,1,1,2,3,5,8,13,21,34,55,89,144,233,377
fib() → 610
This sequence uses a closure over a
[two element] list to hold state.
fcn listUnzip(listOfLists) {
results := T.build(listOfLists[0].len(),0,List);
listOfLists.apply2("reduce",
fcn(n,x,results){results[n].append(x); n+1 },0,results);
results;
} listUnzip(L( L(1,3,5), L(2,4,6) ))
→ L( L(1,2), L(3,4), L(5,6) )
listUnzip(L( L(1,2), L(3,4), L(5,6) )) → L(
L(1,3,5), L(2,4,6) )
Quicksort: parallel and in-place:
// parallel in-place quick sort, ~25% faster with 2
cores (500K random ints)
fcn qipSort(list,cmp='<) {
sp := self.fcn.stranded(Void); // -->
T(N,Void|exception)
fcn(list,left,right,cmp,sp) { // sometimes a Strand
if (left < right) {
// first, partition the list around a pivot, in place
pivotIndex := (left+right)/2; // or median of
first,middle,last
pivot := list[pivotIndex];
list.swap(pivotIndex,right); // move pivot to end
pivotIndex := left;
i := left; do(right-left) { // foreach i in
([left..right-1])
if (cmp(list[i],pivot)) {
list.swap(i,pivotIndex);
pivotIndex += 1;
}
i += 1;
}
list.swap(pivotIndex,right); // move pivot to final
place
// sort the two partitions
if (sp[0] < 10) { // too many strands == too
much overhead
ls := (3000 < pivotIndex - left);
rs := (3000 < right - pivotIndex);
if (ls)
self.fcn.stranded(sp,list,left,pivotIndex-1,cmp,sp);
else self.fcn(list,left,pivotIndex-1,cmp,sp);
if (rs)
self.fcn.stranded(sp,list,pivotIndex+1,right,cmp,sp);
else self.fcn(list,pivotIndex+1,right,cmp,sp);
}
else {
self.fcn(list,left,pivotIndex-1,cmp,sp);
self.fcn(list,pivotIndex+1,right,cmp,sp);
}
}
}(list,0,list.len()-1,cmp,sp);
sp[0].waitFor(0); // when all strands are done, list is
sorted
list;
}
Vector multiply:
fcn vecmul(v1,v2,f)
{v1.apply('wrap(v){v2.apply(f.fp(v))})}
vecmul(L("a","b","c"),L(1,2), '+) →
L( L("a1"), L("a2"),
L("b1"), L("b2"),
L("c1"), L("c2") )
Print stdin prefixed with line
numbers:
Create a file (plines.zkl) containing the following code (there
several ways format this, I picked a "as much as I can left to
right" style):
(fcn(n,line){"%d: %s".fmt(n,line).print(); n+1}) :
File.stdin.reduce(_,1);
zkl plines < VM/data.c | less →
1: /* data.c : the Data object : A container of bytes
2: * Supports both Stream and Sequence semantics
3: * Acts as a bunch of strings, lines or just a jumble of bytes.
4: *
...
|