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:  *
...
     
All rights reserved