Zoomba: A Micro-Language For Jvms .
Zoomba: A Micro-Language For Jvms .
Programming Language
A Micro-Language For JVMs
Version : .
By : zoomba-lang.org
Co n t e n t s
Contents i
Preface ix
A brief History ix
About The Language ix
The Philosophy of the Logo x
Thanks x
About Z o o m BA
. ZoomBA Philosophy
. Design Features
.. ZoomBA is Embeddable
.. Interpreted by JVM
.. ZoomBA can Execute any Java Code
.. ZoomBA can be used Functionally
.. ZoomBA is Dynamically Typed
.. ZoomBA Vs Java
. Setting up Environment
.. Installing Java Run Time
.. Download ZoomBA one jar
.. Add to Path
.. Test Setup
.. Maven Setup for Java Integration
.. Setting up Editors
.. The Proverbial “Hello, World”
ZoomBA S y n ta x i n M i n u t e s
. Building Blocks
.. Identifiers & Reserved Keywords
.. Assignments
.. Comments
.. Basic Types
.. Multiple Assignment
. operators
.. Arithmetic
.. Logical
.. Comparison
.. Ternary
. Conditions
i
ii conten t s
.. If
.. Else
.. Else If
. Loops
.. While
.. For
. Functions
.. Defining
.. Function Calling
.. Global Variables
. Anonymous Function as a Function Parameter
.. Why it is needed?
.. Some Use Cases
. Available Data Structures
.. Range
.. List
.. Set
.. Dict
.. Multi Set
.. Mutability
Formal Co n s t ru c t s
. Conditional Building Blocks
.. Formalism
.. Relation between “there exists” and “for all”
.. There Exist In Containers
.. Size of Containers : empty, size, cardinality
.. There Exist element with Condition : index, rindex , exists
.. For All elements with Condition : select
.. Partition a collection on Condition : partition
.. Collecting value on Condition : select, from
.. Inversion of Logic
. Operators from Set Algebra
.. Set Algebra
.. Set Operations on Collections
.. Collection Cross Product and Power
.. Collection Relation Comparisons
.. Mixing Collections
.. Collections as Tuples
. Assertions
.. Existence of Variables
.. Verifying Truth about Expressions
Collec t i o n s A n d Co m p r e h e n s i o n
. Using Anonymous Argument
.. List
.. Set
.. Dict
.. Multi Set
.. Stack And Queue
.. JVM Arrays
. Constructs Altering the Iteration Flow
contents iii
Types a n d Co n v e r s i o n s
. Integer Family
.. Boolean
.. Integer
.. Arbitrary Large Integer
. Rational Numbers Family
.. Float
.. FLOAT
. Generic Numeric Functions
.. num()
.. size()
.. Float to Integers
.. Signs and Absolute
.. log()
. The Chrono Family
.. time()
.. Adjusting Timezones
.. Comparison on Chronos
.. Arithmetic on Chronos
. String : Using str
.. Types of Strings
.. Null
.. Integer
.. Floating Point
.. Chrono
.. Collections
.. Generalised toString()
.. JSON
.. Yaml
. Playing with Types : Reflection
.. type() function
.. The === Operator
.. The isa operator
.. Field Access
.. Nested Field Access
Reusin g Co d e
iv conten t s
Functi o n a l St y l e
. Functions : In Depth
.. Function Types
.. Default Parameters
.. Named Arguments
.. Arbitrary Number of Arguments
.. Arguments Overwriting
.. Recursion
.. Closure
.. Partial Function
.. Functions as Parameters : Lambda
.. Composition of Functions
.. Operation on Function
.. Eventing
. Strings as Functions : Currying
.. Rationale
.. Minimum Description Length
.. Examples
.. Reflection
.. Referencing
. Avoiding Conditions
.. Theory of the Equivalence Class
.. Dictionaries and Functions
.. An Application : FizzBuzz
. Avoiding Iterations
.. Range Objects in Detail
.. The Fold Functions
.. Matching
.. Sequences
Input a n d O u t p u t
. Reading
.. read() function
.. Reading from url : HTTP GET
.. Reading All Lines
. Writing
.. write(), println(), printf() function family
. File Processing
.. open() function
contents v
.. Reading
.. Writing
. Web IO
.. open() for web
.. get() , post()
.. Sending to url : send() function
. Working with JSON
.. What is JSON?
.. json() function
.. Accessing Fields
.. Yaml Processing
. Working with XML
.. xml() function
.. Converting to Other Formats
.. Accessing Elements
.. XPATH Formulation
. Generic XPath, XElement
.. xpath()
.. xelem()
. DataMatrix
.. matrix() function
.. Accessing Data
.. Tuple Formulation
.. Project and Select
.. The matrix() function
.. Keys
.. Aggregate
Interac t i n g w i t h E n v i r o n m e n t
. Process and Threads
.. system() function
.. popen() function
.. thread() function
.. poll() function
.. Atomic Operations
.. The clock Block
. Handling of Errors
.. error() function
.. Multiple Assignment
.. Error Assignment
. Order and Randomness
.. Lexical matching : tokens()
.. hash() function
.. Anonymous Comparators
.. Sort Functions
.. Heap
.. Priority Queue
.. sum() function
.. minmax() function
.. shuffle() function
.. The random() function
vi conten t s
Practi c a l Z o o m BA
. A Note in Style
.. Comments
.. Naming Conventions
.. Explicit Conditionals and Iteration
.. Assertions
. Assorted Theoretical Examples
.. A Game of Scramble
.. Find Anagrams of a String
.. Sublist Sum Problem
.. Sublist Predicate Problem
.. List Closeness Problem
.. Shuffling Problem
.. Reverse Words in a Sentence
.. Recursive Range Sum
.. String from Signed Integer
.. Permutation Graph
.. Find Perfect Squares
.. Max Substring with no Duplicate
.. Maximum Product of Ascending Subsequence
.. Minimal Sum of Integers in Digit Array
.. Ramanujan Partitions
.. Consecutive Elements in Subset
.. Competitive Array
.. Sum of Permutations
.. Print a String Multiple times
.. Next Higher Permutation
.. Maximal Longest Substring Problem
contents vii
Java Co n n e c t i v i t y
. How to Embed
.. Dependencies
.. Programming Model
.. Example Embedding
. Programming ZoomBA
. Extending ZoomBA
.. Implementing Named Arguments
.. Using Anonymous Arguments
Bibliogra p h y
Index
Index
P r e f ac e
ZoomBA is motivated by Apache Jexl project. Scripting based on JVM is necessary, and
the existing scripting languages were not simply not effective enough for glue purposes. We
believed that world needed a scripting language for JVM, but at the same time, it’s purpose
should be isolated scripting, and with very limited scope. And, ZoomBA was born.
A brief History
Need of the hour was a language where one can write business logic for data processing freely.
There was no language available which lets intermingle with Java POJO.
Worse still - one can not write business logic freely using Java, the whole spring MVC is
a challenge. Given almost all of modern enterprise application are written using Java, it is
impossible to avoid Java and write Enterprise code : in many cases you would need to call
appropriate Java methods to automate APIs.
Thus, one really needs a JVM scripting language that can freely call and act on POJOs, the
inherent historical baggage of JavaScript disables Nashorn from being a suitable choice. But
with Nashorn out of default scripting Engine now, scripting on JVM looks bleak.
A micro language like ZoomBA fills up the gap. When we decided to invent ZoomBA, we
imagined a language where one can freely code data manipulation logic, as a gluing between
multiple web service API calls. It was built to connect the dots, dots being plethora of micro-
services.
ix
x prefac e
Thanks
And finally, all of these pages were typed using TexShop-. So, thanks to Richard Koch, Max
Horn Dirk Olmes. For MacTex, thanks MacTex. You guys are great! Thanks to Apple for
creating such a beautiful system to work on. Steve, we love you. RIP.
Thanks to Gabriel Hjort Blindell - for the beautiful style file he created which can be found
here. Gabriel, thanks a ton. The ZoomBA logo was created using freelogoservices. Keep up the
amazing work.
chapter 1
A b o u t Z o o m BA
A philosophy is needed to guide the design of any system. ZoomBA is not much different.
Here we discuss the rationale behind the language, and showcase how it is distinct from its first
cousin Java. While has similarity to Scala should not come as a surprise, that is an example of
convergent evolution. But just as every modern animal is highly evolved, and there is really
no general purpose animal, we sincerely believe there is no real general purpose language. All
languages are special purpose, some special purposes may seem generic in nature. However,
ZoomBA preaches a different philosophy than scala, kotlin and even python.
. z o o mba philosophy
To begin with, our own experience in Industry is aptly summarised by Ryan Dahl in here , the
creator of Node.js :
I hate almost all software. It’s unnecessary and complicated at almost every layer. At best I can
congratulate someone for quickly and simply solving a problem on top of the shit that they are given.
The only software that I like is one that I can easily understand and solves my problems. The amount
of complexity I’m willing to tolerate is proportional to the size of the problem being
solved...(continued)... Those of you who still find it enjoyable to learn the details of, say, a
programming language - being able to happily recite off if NaN equals or does not equal null - you
just don’t yet understand how utterly fucked the whole thing is. If you think it would be cute to align
all of the equals signs in your code, if you spend time configuring your window manager or editor, if
put unicode check marks in your test runner, if you add unnecessary hierarchies in your code
directories, if you are doing anything beyond just solving the problem - you don’t understand how
fucked the whole thing is. No one gives a fuck about the glib object model. The only thing that
matters in software is the experience of the user. - Ryan Dahl
chapter . about zoom ba
That is,
To boldly go where no developer has gone before - attaining Nirvana in terms of coding
Thus we made ZoomBA so that a language exists with it’s full focus on Business Process Automa-
tion & Validation, not on commercial fads that sucks the profit out of business. Hence it has one
singular focus in mind : brevity but not at the cost of maintainability. What can be done with
people, in days, get it done in day by one person.
It is being demonstrated by firms adapting to it.
. d e s ign features
Here is the important list of features, which make ZoomBA a first choice of the business
developers and software testers, alike.
def parent_function( a ) {
def child_function (b) {
// child can use parents args: happily.
a + b
}
if ( a != null ) {
return child_function
}
}
. Functions are objects, as the above example aptly shows, they can be returned, and
assigned :
fp = parent_function(10)
r = fp(32 ) // result will be 42.
. Closures : the above example demonstrates the closure using partial functions, and this
is something that is syntactically new to Java land.
. s e t ting up environment
Note that you should not have blanks between commas. And then, copy the content of the
vim syntax file here in the $HOME/.vim/syntax/zmb.vim file as is.
If everything is fine, you can now open zmb scripts in vim!
. . s e t t i n g up environment
println(’Hello, World!’)
With some understanding on any of C,C++, Java, Kotlin, Scala, Python or JavaScript then it
will be very easy for you to learn ZoomBA. The biggest syntactic difference between ZoomBA
and other languages is that the ’;’ statement end character is optional. When we consider a
ZoomBA program it can be defined as a collection of functions that communicate via taking
input from the previous and producing output which is to be taken as input to another, next in
line.
. b u i lding blocks
ZoomBA is case-sensitive, which means identifier Hello and hello would have different meaning.
All ZoomBA components require names. Names used for objects, classes, variables and methods
are called identifiers. A keyword cannot be used as an identifier and identifiers are case-
sensitive.
. Fully Reserved : you can not use these identifiers as variables.
if, else, for, while, where, size, empty, def, isa , null, type
atomic, xml, thread, system, lines, list, dict, type , println, read, send, random, hash ,
minmax, fold, index, rindex, int, float, INT, FOAT, bool, num, str.
.. Assignments
Most basic syntax of ZoomBA is, like any other language : assignment.
chapter . zoomba syntax in minut e s
.. Comments
See from the previous subsection "//" used as line comments. Along with the multiline comment
"/*" with "*/" :
a = 1 // Integer
c = ’Hello, ZoomBA’ // String
d = 1.0 // Double
l = 1l // long integer
L = 1L // Large Integer
D = 1.0D // Large Floating
tt = true // boolean
tf = false // boolean
null_literal = null // special null type
a = 1 // Integer
b = 1.0 // float
c = ’Hello, ZoomBA’ // String
// instead, do this straight :
#(a,b,c ) = [ 1 , 1.0 , ’Hello, ZoomBA’ ]
. . o p e r ators
. o p e rators
.. Arithmetic
a = 1 + 1 // addition : a <- 2
z = 1 - 1 // subtraction : z <- 0
m = 2 * 3 // multiply : m <- 6
d = 3.0 / 2.0 // divide d <- 1.5
x = 2 ** 10 // Exponentiation x <- 1024
y = -x // negation, y <- -1024
r = 3 % 2 // modulo, r <- 1
a += 1 // increment and assign
z -= 1 // decrement and assign
x *= 2 // multiply and assign
y /= 3 // divide and assign
a /? b // does a divide b?
2 /? 4 // true
5 /? 13 // false
.. Logical
.. Comparison
.. Ternary
// basic ternary
min = a < b ? a : b // general form (expression)?option1:option2
// try fixing null with it
non_null = a == null? b : a
// or use the null coalescing operator
non_null = a ?? b
// same can be used as definition coalescing
defined = is(a) ? a : b
//same as above
defined = a ?? b
chapter . zoomba syntax in minut e s
. c o n ditions
People coming from any other language would find them trivial.
.. If
x = 10
if ( x < 100 ){
x = x**2
}
println(x) // printlns back x to standard output : 100
.. Else
x = 1000
if ( x < 100 ){
x = x**2
}else{
x = x/10
}
println(x) // printlns back x to standard output : 100
.. Else If
x = 100
if ( x < 10 ){
x = x**2
} else if( x > 80 ){
x = x/10
} else {
x = x/100
}
println(x) // printlns back x to standard output : 10
. l o o ps
.. While
i = 0
while ( i < 42 ){
println(i)
i += 1
}
.. For
For can iterate over any iterable, in short, it can iterate over string, any collection of objects, a
list, a dictionary or a range. That is the first type of for :
. . f u n c t i ons
The result is the same as the while loop. A standard way, from C/C++/Java is to println the
same as in :
There is support for implicit variable for a loop. This variable is called $ :
Just like in Python, we also have a tuple passed as parameter, first one being the index,
second one being the object in question for the iteration:
Scoping rules are simple. foreach is effectively a closed function call. Thus if the iterator
variables references are there outside scope, they gets replaced inside, it is hidden. Once outside
the for loop, they can be accessed again.
. f u n ctions
.. Defining
Functions are defined using the def keyword. And they can be assigned to variables, if one may
wish to.
def count_to_num(n){
for ( i : [0:n] ){
println(i)
}
}
// just assign it
fp = count_to_num
def say_something(word){
return ( "Hello " + word )
}
chapter . zoomba syntax in minut e s
But it is overkill. The last executed statement of a function is the return value, thus :
def say_something(word){
( "Hello " + word ) // returns it
}
$a = 0
x = 0
def use_var(){
$a = 42
println(a) // prints 42
println(x) // prints 0, can access global
x = 42
println(x) // prints 42
}
// call the method
use_var()
println($a) // global a, prints 42
println(x) // local x, prints 0 still
A basic idea about what it is can be found here. As most of the Utility functions use a specific
type of anonymous function, which is nick-named as "Anonymous Parameter" to a utility
function.
l = list()
for ( x : [0:n] ){
l.add ( x * x )
}
return l // yep, here...
Observe that the block inside the *for loop* takes minimal two parameters, in case we println it
like this :
Observe now that we can now create another function, lets call it list_from_list :
def map(x){ x * x }
def list_from_list(fp, old_list)
l = list()
for ( x : old_list ){
// use the function *reference* which was passed
l.add( fp(x) )
}
return l // great...
}
list_from_list(map,[0:n]) // same as previous 2 implementations
The curious block construct after the list function arguments is called anonymous (function)
parameter, which takes over the map function. The loop stays implicit, and the result is
equivalent from the other examples.
This reads as follows : create a list from arguments using the block as ...
The explanation is as follows. For an anonymous function parameter, there are implicit
guaranteed arguments :
. $ –> signifies the iteration object. This has the fields :
. $.o, $.item –> Signifies the item of the collection , we call it the IT EM
. $.c, $.context –> The context, or the collection itself , we call it the CON T EXT
. $.i, $.index –> The index of the item in the collection, we call it the ID of iteration
. Another case to case parameter is : $.p, $.partial –> Signifies the partial result of the
processing , we call it P ART IAL
The ideas can be simply put into examples by taking this particular case :
l = list([1:5]) as {
printf(’item : %s , index : %s , partial %s\n’, $.o,$.i,$.p)
$.o*$.o }
Observe that partial is the partial result of the iteration, which would finally yield to the
final result.
#(min,max) = minmax(1,10,-1,2,4,11)
println(min) // prints -1
println(max) // prints 11
But now, suppose we want to find the minimum and maximum by length of a list of strings. To
do so, there has to be a way to pass the comparison done by length. That is easy :
.. Range
A range is basically an iterable, with start and end separated by colon : [a : b]. We already have
seen this in action. "a" is inclusive while "b" is exclusive, this was designed the standard for loop
in mind. There can also be an optional spacing parameter "s", thus the range type in general is
[a : b : s], as described below:
/*
when r = [a:b:s]
the equivalent for loop is :
for ( i = a ; i < b ; i+= s ){
... body now ...
}
*/
r1 = [0:10] // a range from 0 to 9 with default spacing 1
//a range from 1 to 9 with spacing 2
r2 = [1:10:2] //1,3,5,7,9
.. List
To solve the problem of adding and deleting item from an array, list were invented.
.. Set
A set is a collection of elements such that the elements do not repeat. Thus :
.. Dict
A dictionary is a collection ( a list of ) (key,value) pairs. The keys are unique, they are the
keySet(). Here is how one defines a dict:
od = odict() //
od[100] = 1
od[2] = 2
od[199] = 42
println( od ) // you would find them in the same order...
sd = sdict()
sd[100] = 1
sd[10] = 2
sd[-10] = 42
println( sd ) // you would find them in in sorted key order
l = [ 1,3,4,3,2,1,1,5]
ms = mset(l)
{1=3, 2=1, 3=2, 4=1, 5=1} // MultiSet
What it is depicting is, key has occurred times, occurred once, etc etc This seats in
somewhere between set and a collection, a frequency bucket. mset() and it’s close cousin group()
has other usage, which we will discuss later when we discuss comprehensions on collections.
.. Mutability
Data structures are not generally mutable in ZoomBA. What does that mean?
x = [1,2,3]
x + 10 // add some
println(x) // @[1,2,3] : x did not change
Thus, a variables value never gets changed, unless someone assigns back something to it. The
only way a variable state can get change is through assignment. This is known as Immutability.
See more of a discussion here.
The mutable additive operators : “+=” and “-=” are the ones which do not follow it, because
they are also assignment operators. If the object is part of collection types, they would modify
the left hand object itself, instead of creating a new instance of the object. Thus :
a = [1,2,3]
a + 10 // creates a new array object
s = set(1,2,3)
s+= 42 // simply : s.add(42)
s-= 1 // s.remove(1)
s += [2,3,4] // s.addAll( list(2,3,4) )
s-= [3,4] // s.removeAll( list(3,4) )
m = {1:2, 3:4}
m += {5:6} // m.putAll( {5:6} )
m -= 3 // m.remove(3)
chapter 3
Fo r m a l Co n s tructs
Formalism is the last thing that stays in a software developers mind nowadays. This is a result
of mismanaging expectation of how software and coding is taught and practiced in industry.
The majority view in the Industry is :
It is easy to showcase that this is the prevailing feeling. When the last time people actually did
any mathematics to reach any conclusion like - what would be the optimal object hierarchy
size if any? What should be the optimal pool size for an application? How one can optimally
transform the data?
Most of these cases, a bit of formal thinking solves a lot of the problems that can come due
to bad design, if there was a design in the first place (for most parts, there are none). That
is, because software is nothing but formally applied computer science, and industry lacks
computer science. What can be done by can also be done by people, and no, they do not
have to be really really smart, they only have to be understanding the underlying structure. In
reality software is :
Unless you have an idea of the underlying mechanism, most won’t work, and those who would
luckily work - would simply fail in a slightly longer timeframe. Moreover, user does not care
about brilliant design and optimal code, user care about getting his/her problem solved, fast. It
is business who should care about how to do things optimally and repeatedly, otherwise, the
business model won’t sustain.
This section would be specifically to understand what sort of formalism from computation
we can use in practice to develop and test software to aid business. Because in the end, profit
and revenue runs the show.
.. Formalism
In a formal logic like FOPL, the statements can be made from the basic ingredients, “there exist”
and “for all”. We need to of course put some logical stuff like AN D, OR, and some arithmetic
here and there, but that is what it is.
chapter . formal construc t s
If we have noted down the sorting problem in chapter , we would have restated the problem
as :
There does not exist an element which is less than that of the previous one.
In mathematical logic, “there exists” becomes : ∃ and “for all” becomes ∀. and logical not is
shown as ¬ , So, the same formulation goes in : let
then :
i ∈ S; ∀ i¬ (∃ a[i] s.t. a[i] < a[i − 1])
And the precise formulation of what is a sorted array/list is done in the second order logic.
The problem of programming and validation generally is expressive in terms of higher order
logic.
There is a nice dual relationship between there exists ∃ and ∀. Given we have the negation
operation defined, ∀ and ∃ are interchangeable. Suppose we ask :
∀x ∈ C ; x > 0 ?
Does there exist any element in the collection C less than or equal to ?
∃x ∈ C ; x ≤ 0 ?
To check, if some element is, in some sense exists inside a container ( list, set, dict, array, heap )
one needs to use the IN operator, which is @.
. . c o n d i t ional building blocks
l = [1,2,3,4] // l is an array
in = 1 @ l // in is true
in = 10 @ l // in is false
d = { ’a’ : 10 , ’b’ : 20 }
in = ’a’ @ d // in is true
in = ’c’ @ d // in is false
in = 10 @ d // in is false
in = 10 @ d.values() // in is true
/* division over a dictionary gives the keyset
where the value of the key is the operand */
in = size( d / 10 ) > 0 // in is true
s = "hello"
in = "o" @ s // in is true
/* This works for linear collections even */
m = [2,3]
in = m @ l // true
in = l @ l // true
// the not in operator is defined as such
10 !@ l // true : not in
1 !@ l // false : not in
This was not simply put in the place simply because we simply dislike x.contains(y). In fact
we do dislike the form of object orientation where there is no guarantee that x would be null or
not. Worse, it is impossible to test for null always, such is the prevailing nature of the bad code
in Industry. Formally, then :
// equivalent function
def in_function(x,y){
!empty(x) && x.contains(y)
}
// or one can use simply
y @ x // same result
How about regular expressions? There are two operators related to regular expressions, the
match operator, and then not match operator. See the guide to regular expressions.
re = "^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$"
s = "hello"
match = ( s =~ re ) // false
match = ( s !~ re ) // true
f = "12.3456"
match = ( f =~ re ) // true
match = ( f !~ re ) // false
Thus, the operator “=~” is the match operator, while “!~” is the not match operator.
Regex can be specified verbatim:
me = ’cool!’
me =~ ##^c.*$# // true
me =~ ##c.*# // false
me =~ ##co#g // global match, true
me =~ ##Co#ig // ignore case, global match, true
The right side of the operator, the # operand returns a pattern. It is obvious that the ‘g’ is
for global, ‘i’ is for ignore case. ‘m’ is used for being multiline.
Moreover, to substitute pattern, that is replace with a variable, start with a question mark.
chapter . formal construc t s
me = ’cool!’
p = ’Co’
me =~ ##p#ig // false, p is verbatim
me =~ ##? p#ig // true, p is executed as script first, then substituted
n = null
e = empty(n) // true
n = []
e = empty(n) // true
n = list()
e = empty(n) // true
d = { : }
e = empty(d) // true
nn = [ null ]
e = empty(nn) // false
For the actual size of it, there are two alternatives. One is the size() function :
n = null
e = size(n) // -1
n = []
e = size(n) // 0
n = list()
e = size(n) // 0
d = { : }
e = size(d) // 0
nn = [ null ]
e = size(nn) // 1
Observe that it returns negative given input null. That is a very nice way of checking null.
The other one is the cardinal operator :
n = null
e = #|n| // 0
n = []
e = #|n| // 0
n = list()
e = #|n| // 0
d = { : }
e = #|d| // 0
nn = [ null ]
e = #|nn| // 1
This operator in some sense gives a measure. It can not be negative, so cardinality of null is
also .
. . c o n d i t ional building blocks
l = [ 1, 2, 3, 4, 5, 6 ]
// search an element such that double of it is 6
i = index(l) where { $.o * 2 == 6 } // i : 2
// search an element such that it is between 3 and 5
i = index(l) where { $.o < 5 && $.o > 3 } // i : 3
// search an element such that it is greater than 42
i = index(l) where { $.o > 42 }// i : -1, failed
The way index function operates is: the statements inside the anonymous block are executed.
If the result of the execution is true, the index function returns the index in the collection. If
for none of the elements the anonymous block assumes the true value, it returns a failure by
returning −1.
NOTE that in the ZoomBA negative indices are proper indices into a collection. Thus, a
code like this is tantamount to be a disaster waiting to happen:
l = [ 1, 2, 3, 4, 5, 6 ]
// search an element
i = index(l) where { $.o > 42 } // i : -1
// now we have found the element, so :
x = l[i] // no, x is 6. we must check the index value for >=0
This brings to the problem of actually finding the element, which is solved using find()
function.
l = [ 1, 2, 3, 4, 5, 6 ]
// search an element
mc = find(l) where { $.o > 3 } // result is MonadicContainer
// now we have found the element, so :
r = mc.nil ? mc.value : null // r is 4
Index function runs from left to right, and there is a variation rindex() which runs from
right to left.
l = [ 1, 2, 3, 4, 5, 6 ]
// search an element such that it is greater than 3
i = index(l) :: { $.o > 3 } // i : 3, :: is the where clause
// search an element such that it is greater than 3
i = rindex(l) :: { $.o > 3 } // i : 5
// search an element such that it is greater than 42
i = rindex(l) :: { $.o > 42 } // i : -1, failed
Thus, the there exists formalism is taken care by these operators and functions together.
The function exists() to ensure simpler usage on collections where index is kind of mean-
ingless.
chapter . formal construc t s
l = [ 1, 2, 3, 4, 5, 6 ]
// search an element such that it is greater than 3
// reads there exists item in collection where clause
exists(l) where { $.o > 3 } // true
l = [ 1, 2, 3, 4, 5, 6 ]
// select all even elements
evens = select(l) where { $.o % 2 == 0 }
// for math folks
evens = select(l) :: { 2 /? $.o } // reads : where 2 divides the number
// select all odd elements
odds = select (l) :: { $.o % 2 == 1 }
l = [ 1, 2, 3, 4, 5, 6 ]
#(evens,odds) = partition(l) :: { $.o % 2 == 0 }
println(evens) // prints 2, 4, 6
println(odds) // prints 1, 3, 5
collector = collection()
def condition(item){/* some predicate */}
def map(item){/* some mapping */}
// now the loop
for ( item : collection ){
if ( condition(item) ){
collector.add( map(item ) )
}
}
// use collector
l = [ 1, 2, 3, 4, 5, 6 ]
evens = select(l) where { $.o % 2 == 0 } as { $.o ** 2 }
println(evens) // prints 4, 16, 36
where is the condition function, while as is the mapper function. You can pass the collector
collection also:
this, of course will collect the items in a set. While select() only supports two levels of composi-
tion, one predicate and one mapper, from() supports full functional composition:
So, we would start with all these examples in inverted logic. Observe that select() mandates a
guaranteed runtime of Θ(n), while index() has a probabilistic runtime of Θ(n/2). So, we should
generally choose index() over select(). For example, take the problem of are all numbers in a list
larger than , it can be solve in two ways:
l = [10,2,3,10, 2, 0 , 9 ]
// forall method 1
size(l) == size ( select(l) :: { $.o > 0 } )
// forall method 2 : note the inversion of logic
empty ( select(l) :: { $.o <= 0 } )
// there exists method : note the inversion of condition from 1
!exists(l) :: { $.o <= 0 }
Please choose the rd one, that makes sense, takes less memory, and is optimal.
UAB = A ∪ B := {x ∈ A or x ∈ B} := UBA
chapter . formal construc t s
∆AB := (A \ B) ∪ (B \ A) := ∆BA
b. Equals :
A = B when A ⊆ B and B ⊆ A
c. Proper Subset :
A ⊂ B when A ⊆ B and ∃x ∈ B s.t. x < A
d. Superset Equals:
A ⊇ B when B ⊆ A
e. Superset :
A ⊃ B when B ⊂ A
s1 = set(1,2,3,4)
s2 = set(3,4,5,6)
u = s1 | s2 // union is or : u = { 1,2,3,4,5,6}
i = s1 & s2 // intersection is and : i = { 3,4 }
m12 = s1 - s2 // m12 ={1,2}
m21 = s2 - s1 // m12 ={5,6}
delta = s1 ^ s2 // delta = { 1,2,5,6}
For the lists or arrays, where there can be multiple elements present, this means a newer
formal operation. Suppose in both the lists, an element ‘e’ is present, in n and m times. So,
when we calculate the following :
. Intersection : the count of e would be min(n, m).
. Union : the count of e would be max(n, m).
. . o p e r ators from set algebra
l1 = list(1,2,3,3,4,4)
l2 = list(3,4,5,6)
u = l1 | l2 // union is or : u = { 1,2,3,3,4,4,5,6}
i = l1 & l2 // intersection is and : i = { 3,4 }
m12 = l1 - l2 // m12 ={1,2,3,4}
m21 = l2 - l1 // m12 ={5,6}
delta = l1 ^ l2 // delta = { 1,2,3,4,5,6}
Now, for dictionaries, the definition is same as lists, because there the dictionary can be
treated as a list of key-value pairs. So, for one pair to be equal to another, both the key and the
value must match. Thus:
l1 = [1,2,3]
l2 = [’a’,’b’ ]
cp = l1 * l2
/* [[1, a], [1, b], [2, a], [2, b], [3, a], [3, b]] := cp */
A0 := {} , A1 := A , A2 := A × A
and thus :
An := An−1 × A
For general collection power can not be negative. Here are some examples now:
b = [0,1]
gate_2 = b*b // [ [0,0],[0,1],[1,0],[1,1] ]
another_gate_2 = b ** 2 // same as b*b
gate_3 = b ** 3 // well, all truth values for 3 input gate
gate_4 = b ** 4 // all truth values for 4 input gate
b ** 0 // [] : power zero is empty collection
String is also a collection, and all of these are applicable for string too. But it is a special
collection, so only power operation is allowed.
chapter . formal construc t s
s = "Hello"
s2 = s**2 // "HelloHello"
s_1 = s**-1 // "olleH"
s_2 = s**-2 // "olleHolleH"
One interesting case is that of negative power, which simply reverses a collection, if such
thing is possible :
b = [0,1]
rb = b** -1 // [1 , 0 ]
s = "hello"
rs = s**-1 // "olleh"
b = [0,1]
rb = b** -2 // [[1, 0], [1, 1], [0, 0], [0, 1]]
s = "hello"
rs = s**-2 // "olleholleh"
these are simply not there, in the examples section we will see how they ease out some
interesting algorithms. A very practical use case of power operator for string is padding by
zeros for a binary integer of a fixed size:
n = 23
bn = str(n,2) // convert into binary string
padded_8 = "0" ** (8 - #|bn| ) + bn // 00010111 : convert to 8 bit
s1 = set(1,2,3)
s2 = set(1,3)
sub = s2 < s1 // true
sup = s1 > s2 // true
sube = ( s2 <= s1 ) // true
supe = (s1 >= s2) // true
s3 = set(5,6)
s1 < s3 // false
s3 > s1 // false
s1 != s3 // true
l1 = list(1,2,3,3,4)
l2 = list(1,3,2)
sub = l2 < l1 // true
sup = l1 > l2 // true
sube = ( l2 <= l1 ) // true
supe = (l1 >= l2) // true
l3 = list(5,6)
l1 < l3 // false
l3 > l1 // false
l1 != l3 // true
s = set(1,2,3)
l = list(1,3,3,2)
sub = s < l // true
sup = l > s // true
s == l // false : promotes both to list and checks
u = l | s // u = [1,2,3,3 ]
l = [1,2]
m = [ [1,2],3,4]
in = l @ m // true
The index(), and rindex() generates the index of where the match occurred:
l = [1,2]
m = [1,2,3,4,1,2]
// read which : index is l in m?
forward = index( l , m) // 0
backward = rindex( l , m) // 4
Sometimes it is of importance to get the notion of starts_with and ends_with. There are two
special operators :
chapter . formal construc t s
// read m starts_with l
sw = m #^ l // true
// read m ends with l
ew = m #$ l // true
Note that this is also true for strings, as they are collections of characters. So, these are legal:
s = ’abracadabra’
prefix = ’abra’
suffix = prefix
i = index ( prefix, s ) // 0
r = rindex( suffix, s ) // 7
sw = s #^ prefix // true
ew = s #$ suffix // true
These are not fancy stuffs. These are necessary to move over from the stupidity that is
known as null based programming where lots of efforts gets nullified by the stupidity of null.
Observe, the same null thing:
s = ’abracadabra’
prefix = ’abra’
null #^ s // false
prefix #^ null // false
[null,null] #^ null // true
and thus, we can see where it does the value add. Python fails in this case, because none of
it’s syntaxes permits None type to be used as collection to yield True/False for in or matches.
There is this other operator called InOrder : #@ which tells whether a sub collection exists
in order, as tuple in the larger collection when the larger collection is considered as tuple:
l = [0,1,2,3,4]
[1,2] #@ l // true
[2] #@ l // true
[2,1] #@ l // false
as usual, they are all null ready. The in : @ operator is different from in order, because clearly :
l = [0,1,2,3,4]
[2,1] #@ l // false
[2,1] @ l // true
Thus, in order is precisely what it is, if the items from the passed collection occurs as sub
tuples ( can be build by projection operations ) of the parent collection. Formally, let’s end this
with a bit of formalism as we have started it.
Define a projection operation Πi,j (C) with i ≤ j which slice the collection C from index i to
index j, or rather a sub() function. The following are defined as such :
. In Order :
x #@ C iff ∃(i, j) such that Πi,j (C) = x.
. Starts With :
C #ˆ x iff ∃j such that Π0,j (C) = x.
. Ends With :
C #$ x iff ∃i such that Πi,|C|−1 (C) = x, where |C| is the cardinality of collection C.
. . a s s e rt ions
. a s s ertions
It of utmost important that we do protective coding. What does that mean? That means, for any
function, verify the inputs, and decides, what needs to be done. For the same purpose, there
are some very specific functions which are in place.
If try_expression fails or is null, then assign val to default, else val is try expression.
x = 42
// expression true, so error will not be thrown
assert( x == 42, ’Why x is not 42?’ )
// expression false, so error will not be thrown
panic( x != 42, ’Why x is not 42?’ )
// expression false, so error will BE thrown
assert( x != 42, ’Why x is not 42?’ )
// expression true, so error will BE thrown
panic( x == 42, ’Why x is not 42?’ )
// true, so nothing will happen
test(x == 42, ’I am good’ )
// false, so would log it to standard error
test(x != 42, ’I am not good’ )
All of these functions are arbitrary valued arguments, with one mandatory argument, the
expression which evaluates to true, false. Rest all are processed in, and passed to the runtime, if
the assertion fails. Thus, all passed extra arguments can be used by runtime, thereby extending
the functionality if it is intended to be.
There are guard blocks around each of them. One example is :
What is the difference from the original form? If we use the basic expression form, then, we
do get a variable error that ’x’ does not exist. Which is entirely a different problem. The where
clause is called a guard block, there, expression can be safely executed, to produce true,false.
Obviously, for assert() and test() the clause must return false to fire error, and for panic() it is to
be true.
chapter 4
Co l l e c t i o n s And Comprehension
Comprehension is a method which lets one create newer collection from older ones. In this
chapter we would see how different collections can be made from existing collections.
. u s i ng anonymous argument
.. List
The general form for list and arrays can be written ( as from chapter ):
Obviously list() generates list and list.array() generates array. Any collection structure can be
used as a collector : list, set, oset, sset, dict, sdict, odict, mset . Hence, these are valid :
So, the result of the anonymous block is taken as a function to map the item into a newer
item, and finally added to the final collection.
One can use standard function also in case of anonymous function :
chapter . collections and comprehensio n
.. Set
Set also follows the same behavioural pattern just like list(), but one needs to remember that
the set collection does not allow duplicates. Thus:
Basically, the map function for the set construct, defines the key for the item. Thus, unlike
its cousins list() and array(), the set() function may return a collection with size less than the
input collection.
.. Dict
Dictionaries are special kind of a collection with (key, value) pair with unique keys. Thus,
creating a dictionary would mean uniquely specifying the key, value pair. That can be specified
either in a dictionary tuple way, or simply a pair way:
There is another way of creating a dictionary, that is, passing two collections of same size,
and making first collection as keys, mapping it to the corresponding second collection as values:
k = [’a’,’b’,’c’ ]
v = [1,2,3]
d = dict(k,v)
/* d = { ’a’ : 1 , ’b’ : 2, ’c’ : 3 } */
D : (K, V ) → T ; ti = (ki , vi ) ; ki ∈ K ; vi ∈ V
In essence, what it did is to ‘group’ the collections under multiple groups, all items in the same
group must have same key.
A related problem is to run another function with the collection of each group. This is done
by the group() function. For examples:
. . c o n s t ructs altering the iteration flow
// use as queue
q = list()
for ( x : [0:10] ) { q.enqeue(x) } // enqueue
q.dequeue() // we get back 0
// use as stack
s = list()
for ( x : [0:10] ) { s.push(x) } // push to stak
s.pop() // we get back 10
x = [0,1,2,3]
//@[ 0,1,2,3 ] // ZArray
x.array(’int’)
//0,1,2,3 // int[]
//x.array(’double’)
//0.0,1.0,2.0,3.0 // double[]
x.array(’float’)
//0.0,1.0,2.0,3.0 // float[]
x.array(’long’)
//0,1,2,3 // long[]
x = [true, false]
//@[ true,false ] // ZArray
x.array(’bool’)
//true,false // boolean[]
In normal iterative languages, there are continue and break. The ideas are borrowed from them,
and improved to give a declarative feel onto it.
for ( i : items ){
if ( condition(i) ){
// execute some code
continue
}
// do something else
}
chapter . collections and comprehensio n
That is, when the condition is true, execute the code block, and then continue without going
down further ( not going to do something else ). The idea of the break is :
for ( i : items ){
if ( condition(i) ){
/* execute some code */
break
}
// do something else here
}
That is, when the condition is true, execute the code block, and then break without proceed-
ing with the loop further. Evidently they change the flow of control of the iterations.
.. Continue
As we can see, the condition() is implicit in both break and continue, in ZoomBA this has
become explicit. Observe that:
for ( i : items ){
continue( condition(i) ){ /* continue after executing this */}
// do something else
}
is equivalent of what was being shown in the previous subsection. As a practical example, lets
have some fun with the problem of FizzBuzz, that is, given a list of integers, if n is divisible by
3 print Fizz, if something is divisible by 5, print Buzz, and for anything else print the number
n. A solution is :
Obviously, the block of the continue is optional. Thus, one can freely code the way it was
mentioned in the earlier subsection.
.. Break
As mentioned in the previous subsection, break also has the same features as continue.
for ( i : items ){
break( condition(i) ){ /* break loop after executing this */}
// do something else
}
is equivalent of what was being shown in the previous subsection. As a practical example, lets
find if two elements of two lists when added together generates a given value or not. Formally :
∃(a, b) ∈ A × B ; s.t. a + b = c
. . c o n s t ructs altering the iteration flow
A = [ 1, 4, 10, 3, 8 ]
B = [ 2, 11, 6 , 9 ]
c = 10
for ( p : A*B ){
break( p[0] + p[1] == c ){ printf( ’%d %d\n’ , p[0] , p[1] ) }
}
now, the ls == lc, by definition. More precisely, for a generic select with where clause:
Obviously, the list function can be replaced with any collection type : set, dict.
Similarly, break can be used to simplify conditions :
But it gets used properly in more interesting of cases. What about finding the prime numbers
using the Sieve of Eratosthenes? We all know the drill imperatively:
chapter . collections and comprehensio n
def is_prime( n ){
primes = set(2,3,5)
for ( x : [6:n+1] ){
x_is_prime = true
for ( p : primes ) {
break( x % p == 0 ){ x_is_prime = false }
}
if ( x_is_prime ){
if ( x == n ) return true
primes += x
}
}
return false
}
def is_prime( n ){
primes = list( [2:n+1] ) as {
// store the current number
me = $.o
// check : *me* is not prime - using partial set of primes
not_prime_me = exists( $.p ) where { me % $.o == 0 }
// if not a prime, continue
continue( not_prime_me )
me // collect me, if I am prime
}
// simply check if n is last of the primes
( n == primes[-1] )
}
Observe that, if we remove the comments, it is a one liner. It really is. Hence, declarative style
is indeed succinct, and very powerful. The one reason why we need to create the me variable is
due to the implicit parameter $. The implicit loop variable also as a field $ which access the
parent’s loop parameter. Thus, the same code can be written in a line :
In the previous sections we talked about the comprehensions using a single collection. But we
know that there is this most general purpose comprehension, that is, using multiple collections.
This section we introduce the join() function.
.. Formalism
Suppose we have multiple sets S1 , S2 , ...Sn . Now, we want to generate this collection of tuples
such that the condition ( predicate ) P (e) = true. This is what join really means. Formally, then :
for ( a : A ){
for ( b : B ) {
printf ( ’(%s,%s)’, a,b )
}
}
This is generalised for a cross product of A, B, C, and generally into any cross product. Hence,
the idea behind generating a tuple is nested loop.
Thus, the join() operation with condition() predicate with a mapper() essentially is :
collect = collection()
for ( a : A ){
for ( b : B ) {
tuple = [a,b]
if ( condition ( tuple ) ) { collect += map(tuple) }
}
}
A = [’a’,’b’]
B = [ 0, 1 ]
j1 = A * B // [ [a,0] , [a,1] ,[b,0], [b,1] ]
j2 = join( A , B ) // [ [a,0] , [a,1] ,[b,0], [b,1] ]
But the power of join() comes from the predicate expression one can pass in the anonymous
block. Observe now, if we need permutations of a list of : 3 P2 :
A = [’a’,’b’, ’c’ ]
// generate 2 permutations, when $.o is collection
// one can access the items by index like this
p2 = join( A , A ) where { $.0 != $.1 }
.. Permutations
In the last subsection we figured out how to find permutation. The problem is when we move
beyond , the condition can not be aptly specified as $.0 , $.1. It has to move beyond. So, for
3 P we have :
3
A = [’a’,’b’, ’c’ ]
// generate 3 permutations
p2 = join( A , A, A ) where { #|set($.o)| == #|$.o| }
which is the declarative form for permutation, given all elements are unique. This can be
simply created by the pre-defined perm() function :
chapter . collections and comprehensio n
A = [’a’,’b’, ’c’ ]
// generate 3 permutations
p2 = perm( A, 2 ) // From collection A, generate 2 permutations
// @[ a,b ],@[ b,a ],@[ a,c ],@[ c,a ],@[ b,c ],@[ c,b ] //
CombinatoricsIterable
// hence,
perm(collection, number) // generates an iterable
.. Combinations
What about combinations then? Difference between permutation and combination is, in
combination, order does not matter, hence (1, 2) is same combination as (2, 1), while they are
two distinct permutations.
which is the declarative form for combination, given all elements are unique. This can be
simply created by the pre-defined comb() function :
A = [’a’,’b’, ’c’ ]
// generate 3 combinations
c2 = comb( A, 2 ) // From collection A, generate 2 combinations
@[ a,b ],@[ a,c ],@[ b,c ] // CombinatoricsIterable
comb(collection, number) // generates an iterable
A = [ 1, 4, 10, 3, 8 ]
B = [ 2, 11, 6 , 9 ]
c = 10
v = join(A,B) :: { break( $.0 + $.1 == c) }
/* v := [[1,9]] */
for ( b : [0 : 2 ** n ] ){
bit_map = str(b,2) // binary rep of integer b
bit_map = ’0’ ** ( n - size( bit_map) ) + bit_map
sub_col = list()
for ( i : [0:n] ){
if ( bit_map[i] == _’1’ ){
sub_col += col[i]
}
}
power_col.add( sub_col )
}
println( power_col )
}
This is too much of non declarative hodge-podge. This can be simply coded in as :
a =[0, 1, 2]
// generates all possible combination sequences possible using collection
sequences(a)
a =[0, 1, 2, 3, 4]
a[0:2] // @[0, 1, 2]
Now, given a set of indices, one can define a generic project operation, but that is very easy
to do using collection comprehensions :
a = [0, 1, 2, 3, 4]
indices = [0,2]
p_a = list(indices) as { continue( $.i != $.o ) ; a[$.o] }
This simply selects those indices in order from the collection a. In fact, defining project() or
tuple() is easy :
a = [0, 1, 2, 3, 4]
indices = [from_index,to_index]
p_a = select(a) where { $.i >= indices.0 && $.i < indices.1 ) }
chapter 5
Ty p e s a n d Co nversions
Types are not much useful for general purpose programming, save that they avoid errors.
Sometimes they are necessary, and some types are indeed useful because they let us do many
useful stuff. In this chapter we shall talk about these types and how to convert one to another.
The general form for type casting can be written :
How does this work? The system would try to cast value into the specific type. If it failed,
and there is no default value, it would return null. However, if default is passed, it would
return that when conversion fails. This neat design saves a tonnage of try ... catch stuff.
. i n t eger family
.. Boolean
The syntax is :
chapter . types and conversio n s
.. Integer
This is very useful and the syntax is :
Usage is :
Usage is :
.. Float
The syntax is :
Usage is :
Note that, ZoomBA will automatically shrink floating point data into double, given it can fit the
precision in :
. . g e n e r ic numeric functions
.. FLOAT
This is sometimes required, and the syntax is :
Usage is :
.. num()
For generic numeric conversions, there is num() function, whose job is to convert data types
into proper numeric type, with least storage. Thus :
val = num(value,default_value)
Usage is :
.. size()
size() function finds out digits length for an integer, in a given base:
x = 100
size(x) // 100 in ’unary’ base : imagine 100s of 1’s written
size(x,2) // 7, base 2
size(x,10) // 3 , base 10
size(x,16) // 2 , base 16
chapter . types and conversio n s
These functions takes care of the sign of the number also, and adjusted.
def _sign_(x ){
if ( x < 0 ) return -1
if ( x > 0 ) return 1
0 // else return 0
}
// or we can use :
sign(x) // same as _sign_ function
Absolute of a number is given by either the size() or the operator #|x| to be read as mod-x.
x = -100
#|x| // 100, absolute value of x
size(x) // 100, same
#|null| // 0, mod of nothing is 0
size(null) // -1, shows that the container is un-initialized
.. log()
log() function takes logarithm of a number in Napier’s base, by default, or if base is provided,
on that base:
x = 100
log(x) // 4.6051701859880918 Real
log(10) // 2.0 Double
. t h e chrono family
Handling date and time has been a problem, that too with timezones. ZoomBA simplifies the
stuff. We have Java DateTime to handle date/time:
. . t h e c h rono family
.. time()
This is how you create a DateTime LocalDateTime:
today = time()
dt = time(’20160218’) // 2016-02-18T00:00:00.000+05:30
For all the date formats on dates which are supported, see DateTimeFormat.
Take for example :
dt = time(’2016/02/18’, ’yyyy/MM/dd’ )
// dt := 2016-02-18T00:00:00.000+05:30
dt = time(’2016-02-18’, ’yyyy-MM-dd’ )
// dt := 2016-02-18T00:00:00.000+05:30
dt = time()
dt_honolulu = dt.at( ’Pacific/Honolulu’ )
// dt_honolulu := 2016-02-17T17:23:02.754-10:00
dt_ny = dt.at( ’America/New_York’ )
// dt_ny := 2016-02-17T22:23:02.754-05:00
There is a date() function to convert into a Date object in older Java, as well as timezone:
t = time()
dt = t.date() // convert into Date
dt_honolulu = t.date( ’Pacific/Honolulu’ ) // timezone and covert
t1 = time()
thread().sleep(1000) // wait for some time
t2 = time()
// now compare
c = ( t1 < t2 ) // true
c = ( t2 > t1 ) // true
chapter . types and conversio n s
Tow dates can be equal to one another, but not two instances, that is a very low probability
event, almost never. Thus, equality makes sense when we know it is date, and not instant :
Everything is just a string. It is. Thus every object should be able to be converted to and from
out of strings. There are types of strings in ZoomBA:
.. Null
str() never returns null, by design. Thus:
s = str(null)
s == ’null’ // true
.. Integer
For general integers family, str() acts normally. However, it takes overload in case of IN T () or
BigInteger :
bi = INT(42)
s = str(bi) // ’42’
s = str(bi,2) // base 2 representation : 101010
d = 101.091891011
str( d, 0 ) // 101
str(d,1) // 101.1
str(d,2) // 101.09
str(d,3) // 101.092
.. Chrono
Given a chrono family instance, str() can convert them to a format of your choice. These formats
have been already discussed earlier, here they are again: SimpleDateFormat.
The syntax is :
t = time()
str( t ) // default is ’yyyyMMdd’ : 20160218
str( t , ’dd - MM - yyyy’ ) // 18 - 02 - 2016
str( t , ’dd-MMM-yyyy’ ) // 18-Feb-2016
chapter . types and conversio n s
.. Collections
Collections are formatted by str by default using ‘,’. The idea is same for all of the collections,
thus we would simply showcase some:
l = [1,2,3,4]
s = str(l) // ’1,2,3,4’
s == ’1,2,3,4’ // true
s = str(l,’#’) // ’1#2#3#4’
s == ’1#2#3#4’ // true
l = [1,2,3,4]
m = [ ’a’ , ’b’ , ’c’ ]
j = l * m // now this is a list of lists, really
s_form = str(j, ’&’) as { str($.o,’#’) } // lineraize
// This generates 1#a&1#b&1#c&2#a&2#b&2#c&3#a&3#b&3#c&4#a&4#b&4#c
.. JSON
Given we have a complex object comprise of primitive types and collection types, the jstr()
function returns a JSON representation of the complex object. This way, it is very easy to
inter-operate with JavaScript type of languages.
d = { ’a’ : 10 , ’b’ : 20 }
s = jstr(d) // as json string
/* { "a" : 10 , "b" : 20 } */
// pretty print json string using ZoomBA native capability
jstr(d,false)
jstr(d,true) // pretty print, using Jsoner capability
.. Yaml
Given we have a complex object comprise of primitive types and collection types, the ystr()
function returns a Yaml representation of the complex object.
d = { ’a’ : 10 , ’b’ : 20 }
s = ystr(d) // as Yaml string
/* Yaml String ...
a: 10
b: 20
*/
. . p l ay i n g with types : reflection
It is essential for a dynamic language to dynamically inspect types, if at all. Doing so, is termed
as reflection. In this section, we would discuss different constructs that lets one move along the
types.
d = { : }
c1 = type(d) // c1 := java.util.HashMap
i = 42
c2 = type(i) // c2 := java.lang.Integer
c3 = type(20) // c3 := java.lang.Integer
c3 == c2 // true
c1 == c2 // false
a = [1,2,3]
ca = type(a) // ZArray
b = list(1,3,2)
cb = type(b) // ZList type
a == b // true
ca == cb // false
// type equals would be :
ca == cb && a == b // false
But that is a long haul. One should be succinct, so there is this borrowed operator from
JavaScript, known as “===” which let’s its job in a single go :
a = [1,2,3]
b = list(1,3,2)
a === b // false
c = [3,1,2]
c === a // true
This also showcase the issues of completely overhauling the type structure found in a JVM. The
first two lines are significantly quirky.
There is a distinctly better way of handling isa, by using patternmatcher strings, strings
that starts with ## and has a type information as expression match: e.g. ##map#ig :
b = list(1,3,2)
b isa ##list#ig // true : b is a type of list ?
{:} isa ##map#ig // true
4.2 isa ##double##ig // the double
s = set(1,2,3)
s isa ##set#ig // true
t = time()
t is a ##date#ig // true : for ZDate
This regex also is case insensitive, and matches from anywhere, so be careful.
d = time()
d.date().fastTime // some value
There is obvious way to access properties as if the object is a dictionary, or rather than a
property bucket:
d = time().date()
d[’fastTime’] == d.fastTime // true
This opens up countless possibilities of all what one can do with this. Observe however,
monsters like spring and hibernate is not required given the property injection is this simple.
Of course, it can actually be a proper java object ( we are simply using JSON map ). How to
access upto the level ‘m.a.b’ ? Or rather, how to get back the list of all nested fields "b" ? This is
the precise reason ZoomBA has xpath() and xelem() functions.
One can actually parameterize the access "m.a.b" using the xpath() function:
. . p l ay i n g with types : reflection
What about there are multiple matches? This is done by optional third argument sending a
boolean ‘true’ :
But this pose a little problem of the setting property side of the things. Moreover, the power
to traverse the object tree wanes off with only values. For this, there is the xelem() function:
p.value = 42
// now if we print m :
println(m) // prints {"a" : {"b" : 10, "x" : 42}, "c" : {"b" : 20} }
xelem() also takes another optional parameter to get back a list of pointers:
And now we can go about merry ways of getting and setting values in them.
chapter 6
R e u s i n g Co d e
The tenet of ZoomBA is : “write once, forget”. That essentially means that the code written has
to be sufficiently robust. It also means that, we need to rely upon code written by other people.
How does this work? The system must be able to reuse any code from Java SDK, and should
be able to use any code that we ourselves wrote. This chapter would be elaborating on this.
. t h e import directive
.. Syntax
Most of the languages choose to use a non linear, tree oriented import. ZoomBA focuses on
minimising pain, so the import directive is linear. The syntax is simple enough :
The directive imports the stuff specified in the import_path and create an alias for it, which
is :
unique_import_identifer, Thus, there is no name collision at all in the imported script. This
unique_import_identifer is known as the namespace.
.. Examples
Observe, if you want to import the class java.lang.Integer :
This directive would import the class, and create an alias which is Int. To use this class
further, now, we should be using :
Int.parseInt(’20’) // int : 20
Int.valueOf(’20’) // int : 20
chapter . reusing cod e
Once we have this class now, we can choose to instantiate it, See the manual of this class
here:
And thus, we just created a Java class instance. This is how we call Java objects, in general
from ZoomBA.
Calling methods now is easy:
Note that thanks to the way ZoomBA works, a method of the form getXyz is equivalent to a
field call xyz so :
. u s i ng zoomba scripts
We have already discussed how to import an ZoomBA script, so in this section we would discuss
how to create a re-usable script.
/* hello.zm */
def say_hello(arg){
println(’Hello, ’ + str(arg) )
}
s = "Some one"
say_hello(s)
/* end of script */
but, the issue is when the runtime loads it, the relative path would with respect to the
runtimes run directory. So, relative path, relative to the caller would become a mess. To solve
this problem, relative import is invented.
In this scenario, the runtime notices the “_/”, and starts looking from the directory the
caller.zm was loaded! Thus, without any P AT H hacks, the ZoomBA system works perfectly
fine.
import ’some/path/file’ as NS
NS.function(args,... )
If one needs to call the whole script, as a function, that is also possible, and that is done
using the execute() function:
import ’some/path/file’ as NS
NS.execute(args,... )
We will get back passing arguments to a function in a later chapter. But in short, to call hello.zm,
as a function, the code would be :
Just as python has the implicit variable __name__ ==0 __main__0 to identify if a script is
running as ‘main’ script or not, ZoomBA has the implicit variable @SCRIP T . This stores the
current script. Thus :
// a.zm
import ’b’ as B
B.say_something("hello!") // this is how you call a function
. . u s i n g zoomba scripts
// b.zm
def say_something(arg){
println("I am from B!" + arg )
}
// body will be executed only when the script is main
if ( @SCRIPT.main ){
say_something(@ARGS)
}
// b.zm
println("I am being called - from B!")
def say_something(arg){
println("I am from B!" + arg )
}
// body will be executed only when the script is main
if ( @SCRIPT.main ){
say_something(@ARGS)
}
Now, if we run the ‘a.zm’ file we see the line "I am being called - from B!". This has very
interesting implications. It means all executable blocks will be executed, and all variables
which are associated with the imported scripts block, will be initialised and accessible from the
importing module. For all practical purpose, an imported module acts same as an object :
// a.zm
import ’b’ as B
println( B.TI ) // this is how you access free variables defined on imported
module
// b.zm
TI = time() // time of import ?
if ( @SCRIPT.main ){
println("From B : " + TI)
}
Naturally, we can extend an module import with initialisation of arguments, which will not
be trivial, and hence, there is no need to invent Objects over imported modules.
chapter 7
Fu n c t i o n a l Style
. f u n ctions : in depth
As the functional style is attributed to functions, in this section we would discuss functions in
depth.
def my_function( a, b ){ a + b }
l = list([0:10] ) as { $.o ** 2}
// same with a name-less function ( against anonymous )
l = list([0:10]) as def(_index, _item,_partial,_context) { _item** 2 }
// note that , all arguments are optional, so :
l = list([0:10]) as def() { item = @ARGS[1] ; item **2 } // works too
. Implicit Functions : This functions are not even functions, they are the whole script
body, to be treated as functions. Importing a script and calling it as a function qualifies
as one :
chapter . functional sty l e
Every defined function in ZoomBA is capable of taking default values for the parameters. For
example :
Note that, one can not mix the default and non default arbitrarily. That is, all the default
arguments must be specified from the right side. Thus, it is legal :
But it is not :
In ZoomBA, one can change the order of the parameters passed, provided one uses the named
arguments. See the example:
Note that, named args can not be mixed with unnamed args, that is, it is illegal to call the
method this way :
Every ZoomBA function can take arbitrary no of arguments. To access the arguments, one must
use the @ARGS construct, as shown :
. . f u n c t i ons : in depth
This means that, when a function expects n parameters, but is only provided with m < n
parameters, the rest of the expected parameters not passed is passed as null.
In the same way when a function expects n parameters, but is provided with m > n parame-
ters, the rest of the passed parameters can be accessed by the @ARGS construct which was the
first example.
l = [’a’,’b’,’c’ ]
perm_3_from_3 = join(l,l,l) where { #|set($.o)| == #|$.o| }
l = [’a’,’b’,’c’ ,’d’ ]
perm_4_from_4 = join(l,l,l,l) where { #|set($.o)| == #|$.o| }
perm_2_from_4 = join(l,l) where { #|set($.o)| == #|$.o| }
Thus, how to generate the general permutation? As we can see, the arguments to the
permutation is always varying. To fix this problem, argument overwriting was invented. That, in
general, all the arguments to a function can be taken in runtime from a collection.
l = [’a’,’b’,’c’ ,’d’ ]
// this call overprintlns the args :
perm_2_from_4 = join( @ARGS = [l,l] ) where{ #|set($.o)| == #|$.o| }
.. Recursion
It is customary to introduce recursion with factorial. We would not do that, we would introduce
the concept delving the very heart of the foundation of mathematics, by introducing Peano
Axioms. Thus, we take the most trivial of them all : Addition is a function that maps two
natural numbers (two elements of N) to another one. It is defined recursively as:
/* successor function */
def s( n ){
if ( n == 0 ) return 1
return ( s(n-1) + 1 )
}
/* addition function */
def add(a,b){
if ( b == 0 ) return a
return s ( add(a,b-1) )
}
These functions do not only show the trivial addition in a non trivial manner, it also shows
that the natural number system is recursive. Thus, a system is recursive if and only if it is in -
correspondence with the natural number system. Observe that the function add() does not even
have any addition symbol anywhere! This is obviously cheating, because one can not use minus
operator to define plus operation. A better less cheating solution is to use function composition
and power operator : a + b := s(...s(s(a))) b times, that is sb (a), which we would discuss later.
Now we test the functions:
println(s(0)) // prints 1
println(s(1)) // prints 2
println(s(5)) // prints 6
println(add(1,0)) // prints 1
println(add(1,5)) // prints 6
println(s(5,1)) // prints 6
def fact(n){
if ( n <= 0 ) return 1
return n * fact( n - 1 )
}
.. Closure
A closure is a function, whose return value depends on the value of one or more variables
declared outside this function. Consider the following piece of code with anonymous function:
multiplier = def(i) { i * 10 }
Here the only variable used in the function body, i ∗ 0, is i, which is defined as a parameter
to the function. Now let us take another piece of code:
There are two free variables in multiplier: i and factor. One of them, i, is a formal parameter
to the function. Hence, it is bound to a new value each time multiplier is called. However, factor
is not a formal parameter, then what is this? Let us add one more line of code:
factor = 3
multiplier = def (i) { i * factor }
Now, factor has a reference to a variable outside the function but in the enclosing scope. Let
us try the following example:
Above function references factor and reads its current value each time. If a function has no
external references, then it is trivially closed over itself. No external context is required.
list(1,2,3,4) as { $.o ** 2 }
The {$.o ∗ ∗2} is a lambda. The parameter is implicit albeit, because it is meaningful that way.
However, sometimes we need to pass named parameters. Suppose we now want to create a
function composition, first step would be to apply it to a single function :
And now, we just created a lambda! The result of such an application would make apply
function returning .
Functions support composition to generate a new function : The operator for function composi-
tion :
# < f |g > (x) := g(f (x))
def f(){
println(str(@ARGS,’#’))
[ @ARGS[0] ** 2 ]
}
def g(){
println(str(@ARGS,’#’))
[ @ARGS[0] - 2 ]
}
h = #< f | g > // g of f
println( h(10) )
The result is :
10
100
@[ 98 ]
.. Eventing
All ZoomBA functions are Eventing ready. What are events? For all practical purposes, an event
is nothing but a hook before or after a method call. This can easily be achieved by the default
handlers for ZoomBA functions. See the object Event.
Here is a sample demonstration of the eventing:
def my_func(){
printf(’ARGS : %s %n’, str(@ARGS,’#’))
}
def event_hook(event){
printf(’%s %s %n’, event.method.name , event.when )
}
my_func.before += event_hook
my_func.after += event_hook
my_func(’hello’, ’world’)
All these idea started first with Alan Turing’s Machine, and then the rd implementation of a
Turing Machine, whose innovator Von Neumann said data is same as executable code. Read
more on : Von Neumann Architecture. Thus, one can execute arbitrary string, and call it code,
if one may. That brings in how functions are actually executed, or rather what are functions.
.. Rationale
The idea of Von Neumann is situated under the primitive notion of alphabets as symbols, and
the intuition that any data is representable by a finite collections of them. The formalisation of
such an idea was first done by Kurt Godel, and bears his name in Gödelization.
For those who came from the Computer Science background, thinks in terms of data as
binary streams, which is a general idea of Kleene Closure : {0, 1}∗ . Even in this form, data is
nothing but a binary String.
chapter . functional sty l e
Standard languages has String in code. In ‘C’ , we have “string” as “constant char*”. C++
gives us std:string , while Java has “String”. ZoomBA uses Java String. But, curiously, the whole
source code, the entire JVM assembly listing can be treated as a String by it’s own right! So,
while codes has string, code itself is nothing but a string, which is suitable interpreted by a
machine, more generally known as a Turing Machine. For example, take a look around this :
(zoomba)println(’Hello, World!’)
This code printlns ‘Hello, World!’ to the console. From the interpreter perspective, it
identifies that it needs to call a function called println, and pass the string literal “Hello, World!”
to it. But observe that the whole line is nothing but a string.
This brings the next idea, that can strings be, dynamically be interpreted as code? When
interpreter reads the file which it wants to interpret, it reads the instructions as strings. So, any
string can be suitable interpreted by a suitable interpreter, and thus data which are strings can
be interpreted as code.
.. Examples
First problem we would take is that of comparing two doubles to a fixed decimal place. Thus:
(zoomba)x=1.01125
=>1.01125
(zoomba)y=1.0113
=>1.0113
(zoomba)x == y
=>false
How about we need to compare these two doubles with till th digit of precision (rounding)
? How it would work? Well, we can use String format :
(zoomba)str("%.4f",x)
=>1.0113
(zoomba)str("%.4f",y)
=>1.0113
. . s t r i n g s as functions : currying
But wait, the idea of precision, that is "." should it not be a parameter? Thus, one needs to
pass the precision along, something like this :
(zoomba)c_string = "%%.%df" ## This is the original one
=>%%.%df
## apply precision, makes the string into a function
(zoomba)p_string = str(c_string,4)
=>%.4f
## apply a number, makes the function evaluate into a proper value
(zoomba)str(p_string,y)
=>1.0113 # and now we have the result!
All we really did, are bloated string substitution, and in the end, that produced what we
need. Thus in a single line, we have :
(zoomba)str(str(c_string,4),x)
=>1.0113
(zoomba)str(str(c_string,4),y)
=>1.0113
if ( operation == ’+’ ) {
do_plus_check();
} else if ( operation == ’-’ ) {
do_minus_check();
}
// some more code ...
switch ( operation ){
case ’+’ :
do_plus_check(); break;
case ’-’ :
do_minus_check(); break;
...
}
We need to test something of the form we call a "binary operator" is working "fine" or not:
That is a general binary operator. If someone can abstract the operation out - and replace
the operation with the symbol - and then someone can actually execute that resultant string
as code (remember JVMA?) the problem would be solved. This is facilitated by the back-tick
operator ( executable strings) :
(zoomba)c_string = #’#{a} #{op} #{b}’
=>#{a} #{op} #{b}
(zoomba)a=10
=>10
(zoomba)c_string = #’#{a} #{op} #{b}’
=>10 #{op} #{b}
(zoomba)op=’+’
=>+
(zoomba)c_string = #’#{a} #{op} #{b}’
=>10 + #{b}
(zoomba)b=10
=>10
(zoomba)c_string = #’#{a} #{op} #{b}’
=>20
.. Reflection
Calling methods can be accomplished using currying.
The f unc is just the name of the function, not the function object at all. Thus, we can use this
to call methods using reflection.
.. Referencing
Let’s see how to have a reference like behaviour in ZoomBA.
(zoomba)x = [1,2]
@[1, 2]
(zoomba)y = { ’x’ : x }
{x=@[ 1,2 ]}
(zoomba)x = [1,3,4]
@[1, 3, 4]
(zoomba)y.x // x is not updated
@[1, 2]
Suppose you want a different behaviour, and that can be achieved using Pointers/References.
What you want is this :
(zoomba)x = [1,2]
@[1, 2]
(zoomba)y = { ’x’ : ’x’ }
{x=x}
(zoomba)#’#{y.x}’ // access as in currying stuff
@[1, 2]
(zoomba)x = [1,3,4]
@[1, 3, 4]
(zoomba)#’#{y.x}’ // as currying stuff, always updated
@[1, 3, 4]
So, in effect we are using a dictionary to hold name of a variable, instead of having a hard
variable reference, thus, when we are dereferencing it, we would get back the value if such a
value exists!
. . avo i d i ng conditions
As we can see the tenets of functional programming says to avoid conditional statements. In
the previous section, we have seen some of the applications, how to get rid of the conditional
statements. In this section, we would see in more general how to avoid conditional blocks.
Conditionals boils down to if − else construct. Observe the situation for a valid date in the
format of ddMMyyyy.
is_valid_date(d_str){
num = int(d_str)
days = num/1000000
rest = num % 1000000
mon = rest /10000
year = rest % 10000
max_days = get_days(mon,year)
return ( 0 < days && days <= max_days &&
0 < mon && mon < 13 &&
year > 0 )
}
get_days(month,year){
if ( month == 2 and leap_year(year){
return 29
}
return days_in_month[month]
}
days_in_month = [31,28,31,... ]
This code generates a decision tree. The leaf node of the decision tree are called Equivalent
Classes, their path through the source code are truly independent of one another. Thus, we can
see there are equivalence classes for the days:
. Months with days
. Months with days
. Month with days : Feb - non leap
. Months with days : Feb -leap
And the if − else simply ensures that the correct path is being taken while reaching the
equivalent class. Observe that for two inputs belonging to the same equivalent class, the code
path remains the same. That is why they are called equivalent class.
Note from the previous subsection, that the days of the months were simply stored in an
array. That avoided some unnecessary conditional blocks. Can it be improved further? Can we
remove all the other conditionals too? That can be done, by intelligently tweaking the code.
Observe, we can replace the ifs with this :
get_days(month,year){
max_days = days_in_month[month] +
( (month == 2 and leap_year(year) )? 1 : 0 )
}
Note that both the conditions are the same, except the switch of > in the ascending to <
in the descending. How to incorporate such a change? The answer lies in the dictionary and
currying :
In this form, the code written is absolutely generic and devoid of any explicit conditions.
Dictionaries can be used to store functions.
/* Pure If/Else */
def fbI(range){
for ( i : range ){
if ( i % 3 == 0 ){
if ( i % 5 == 0 ){
println(’FizzBuzz’)
} else {
println(’Fizz’)
}
}else{
if ( i % 5 == 0 ){
println(’Buzz’)
}else{
println(i)
}
}
}
}
However, it can be written in a purely declarative way. Here is how one can do it, Compare this
with the other one:
def fbD(range){
d = { 0 : ’FizzBuzz’ ,
3 : ’Fizz’ ,
5 : ’Buzz’ ,
6 : ’Fizz’,
10 : ’Buzz’ ,
12 : ’Fizz’ }
for ( x : range ){
r = x % 15
continue ( r @ d ){ println( d[r] ) }
println(x)
. . avo i d i ng iterations
}
}
The last tenet is avoiding loops, and thus in this section we would discuss how to avoid loops.
We start with some functions we have discussed, and some functionalities which we did not.
d_start = time()
d_end = d_start + "PD10"
// a range from current to future
d_range = [d_start : d_end ]
// another range with 2 days spacing
d_range_2 = [d_start : d_end : 2 ]
and this way, we can iterate over the dates or rather time. The spacing is to be either an
integer, in which case it would be taken as days, or rather in string as the ISO-TimeInterval-
Format. The general format is given by : P yY mMwW dDT hHmMsS.
The other one is the sequence of symbols, the Symbol range :
s_s = "A"
s_e = "Z"
// symbol range is inclusive
s_r = [s_s : s_e ]
// str field holds the symbols in a string
s_r.str == ’ABCDEFGHIJKLMNOPQRSTUVWXYZ’ // true
r = [0:5]
// a list of the elements
r.list()
// reverse of the same [a:b]
r.reverse() // [4, 3, 2, 1, 0]
// inverse of the range : reversed into [b:a]
r.inverse() // [5:0:-1]
There is the ‘XRange’. X Range stands for extended range, which goes beyond standard size
of JVM. This can be created by using long types for one or more end points of a range :
It is obvious that list() and related functions may or may not be possible with XRange.
chapter . functional sty l e
The idea can be found here in a more elaborative way. We must remember that the partial exists,
and the general fold function is defined as : f old() folds from left, so it is also called lf old().
items = [0,1,2,3,4]
rfold( items ) as { printf( ’%s ’, $.o) }
This prints :
4 3 2 1 0
Let us showcase some functionalities using fold functions, we start with factorial :
l = [1,8,1,2,3,4,5]
#(min,max) = lfold( l , [l.0, l.0] ) as {
#(m,M) = $.p
continue( $.o > M ){ $.p = [ m, $.o] }
continue( $.o < m ){ $.p = [ $.o, M] }
$.p // return it off, when nothing matches
}
// (1,8)
. . avo i d i ng iterations
So, as we can see, the fold functions are the basis of every other functions that we have seen.
We show how to replace select function using fold:
To do an index() function :
.. Matching
There is if, and there is else and switch-case seems to be missing in ZoomBA. Fear not, the
omniscient #match exists. To demonstrate :
// match item
item = 10
selected = switch(item){
case 1 : "I am one"
case @$ < 5 : "I am less than 5"
/* Default match is @$ itself, note that :
inside match you must use block comments */
case @$ : "yes, I could not match anything!"
}
. Any expression is permitted in the case statement, switch returns the body value of the
matching case. Thus, unlike ordinary switch-case, it returns a proper value. Thus, it is
more succinct to put it at the end of a choice function.
.. Sequences
Iteration is key component of multitude of algorithms. Let’s start with Factorial, Fibonacci,
which are recursively defined. We did find out how to do it with basic function style. Observe,
the recursion pattern Fn+1 = nFn , with base case F(0) = 1.
This is done by the seq() function in ZoomBA, which generates the iterator:
And that is how we can generate a never ending sequence of factorials. Moreover, we can see
the ‘history’. Formally, seq() is a function that is capable of computing fixed point iteration :
Xn+1 = F(Xn , Xn−1 , ...), each of these Xn−i can be tracked by the $.item term, or history.
While Factorial only use one past history term to calculate next, Fibonacci, uses :
with base cases Fib(0) = Fib(1) = 1 and a straightforward mapping is possible using se-
quences:
It concludes the sequences. One has to remember that a sequence is a never ending iterator.
chapter 8
I n p u t a n d O u tput
IO is of very importance for software testing and business development. There are two parts
to it, the generic IO, which deals with how to read/write from disk, and to and from url. There
is another part of the data stuff, that is structured data. ZoomBA let’s you do both, and those
are the topic of this chapter.
. r e ading
value = read(source_to_read)
The source can actually be a file, an UNC path, an url. Note that given it is a file, read() reads
the whole of it at a single go, so, be careful with the file size.
resp = read(’http://www.google.co.in’)
/* value contains response of the google home page */
resp.body() // string of HTML
resp.bytes // response bytes
resp.code // responce code
resp.headers // response headers
chapter . input and outpu t
_url_ = ’https://httpbin.org/get’
params = { ’foo’ : ’bar’ , ’complexity’ : ’sucks’ }
// str: is the namespace alias for ’java.lang.String’
data = str(params.entries,’&’) as {
str(’%s=%s’,$.key,$.value)
}
response = read( str("%s?%s", _url_ , data ) )
In short, the f ile() method yields an iterator. But with this, it reads the whole file together as
list of string lines:
This is again a synchronous call, and thus, it would be advisable to take care.
. w r i ting
println(’Hello,World!’) ; // prints it
printf("%d", 10) // formatted print
eprintln("This goes to error stream!" )
eprintf("This goes to error : %d", 10 ) // formatter
Given that the first argument does not contain a "%" symbol, the write functionality also
writes the whole string to a file with the name :
. . f i l e p rocessing
. f i l e processing
We talked about a bit of file processing in read and write. But if we want to read a file line by
line, then we have to use something else.
.. Reading
To read, one must open the file in read mode. This returns a java BufferedReader object. Now,
something like readLine can be called to read the file line by line, in a traditional way to
implement the cat command :
.. Writing
To write, one must open the file in write or append mode. This returns a java PrintStream
object. Now, something like println() can be called to write to the file :
. w e b io
Once we have open() a http or https connection, one can get or post into it by appropriate
functions :
To write back to url, or rather for sending anything to a server, there is a specialised send()
function.
_url_ = ’https://httpbin.org’
path = "/post"
wcon = open(_url_)
params = { ’foo’ : ’bar’ , ’complexity’ : ’sucks’ }
// path , protocol, headers, body
resp = wcon.send( post , "POST" , {:} , jstr(params) )
The SOAP XML stuff can be easily done with send. See the example soap body we will send
to :
template_pay_load = read(’samples/soap_body.xml’)
pay_load = str( template_pay_load , ’Hyderabad’, ’India’ )
headers = { "SOAPAction" : "http://www.webserviceX.NET/GetWeather" ,
"Content-Type" : "text/xml; charset=utf-8" }
wcon = open( "http://www.webservicex.net")
resp = wcon.send( "/globalweather.asmx" , "POST" , headers , pay_load )
x = xml(resp.body() )
sr = x.element("//GetWeatherResult")
write(sr.text)
That definition ensures that the ordering layout of the properties does not matter at all.
Clearly :
{“x00 : 10, “y 00 : 42} == {“y 00 : 42, “x00 : 10}
Thus, we reach the evident conclusion, JSON are nothing but Dictionaries. Hence in ZoomBA
they are always casted back to a dictionary.
/* sample.json file */
{
"zooomba": {
"dbName": "zooomba",
"url": "jdbc:postgresql://localhost:5432/zoomba",
"driverClass" : "org.postgresql.Driver",
"user": "zooomba",
"pass": ""
},
"some2": {
"dbName": "dummy",
"url": "dummy",
"driverClass" : "class",
"user": "u",
"pass": "p"
},
}
jo = json(’sample.json’, true)
The json() function can read also from the string argument. The return result object is either a
Dictionary object, or an array object, based on what sort of json structure was passed.
chapter . input and outpu t
jo.noga.dbName // "zoomba"
jo.some2.driverClass // "class"
In effect, one can imagine this is nothing but a big property bucket. For some, there is this thing
called JSONPath, which is clearly not implemented in ZoomBA, because that is not a standard.
In any case, given json is a string, to check whether a text exists or not is a regular string search.
Given whether a particular value is in the hierarchy of the things or not, that is where the stuff
gets interesting.
For parameterised access, Currying is a better choice:
prop_value = ’zoomba.dbName’
value = #‘jo.#{prop_value}‘ // same thing, "zoomba"
In this way, one can remove the hard coding of accessing JSON objects.
Only important distinction is, for Yaml processing, the parser does not support automatic
type conversion into primitive type, thus, type hints are needed.
Please, do not work with xml. There are many reasons why xml is a terrible idea. The best of
course is :
XML combines the efficiency of text files with the readability of binary files – unknown
But thanks to many big enterprise companies - it became a norm to abused human intellect
- the last are obviously Java EE usage in Hibernate, Springs and Struts. Notwithstanding the
complexity and loss of precise network bandwidth - it is a very popular format. Thus - against
my better judgment we could not avoid XML.
Call xml() To load the xml file ( or a string ) into an ZXml object :
The encoding string ( UTF- ) is optional. Default is UTF-. The full syntax of xml() is :
// x is a ZXml object
x = xml( source [, is_source_file? false , encoding=UTF-8, validate? false )
// x is a ZXml object
x = xml( ... )
json_object = json(x) // converts xml to json object
json_string = jstr(x) // converts xml to json string
yaml_string = ystr(x) // converts xml to yaml string
// XmlMap of the x
y = xml(x)
y.root.children[0].name //glossary
y.root.children[0].children[0].text // example glossary
// XmlMap of the x
x = xml(’sample.xml’,true) // slideshow xml
x.root.name //slideshow
// access attributes
x.root.children[0].attr.type // "all"
x.root.children[0].attr[’type’] // "all"
The other way of accessing elements, is to use the element() function, which uses XPATH :
// XmlMap of the x
x = xml(’sample.xml’, true ) // slideshow xml
// get one element
e = x.element("//slide/title")// first title element
e.text // "Wake up to WonderWidgets!"
chapter . input and outpu t
In the same way, for selecting multiple elements, elements() function is used :
// XmlMap of the x
x = xml(’sample.xml’) // slideshow xml
// get all the elements called titles
es = x.elements("//slide/title" ) // a list of elements
// print all titles ?
fold(es) as { println( $.text ) }
// XmlMap of the x
x = xml(’sample.xml’,true) // slideshow xml
// text of the title element
s = x.xpath("//slide/title/text()" )
// XmlMap of the x
x = xml(’sample.xml’,true) // slideshow xml
// text of the title element, exists?
b = x.exists("//slide/title/text()" ) // bool : true
b = x.exists("//slide/title" ) // bool : true
b = x.exists("//foobar" ) // bool : false
Obviously, one can mix and match, that is, give a default to the xpath() function :
In any declarative flavoured language, it is imperative that automatic discovery of fields and
dynamic accessing of fields to be supported. For XML, people do have xpath, while for JSON a
non standard json path is coming recently to the foray. ZoomBA uses apache jxpath to simplify
the whole problem.
JXPath can be used for any complex objects, including Maps and Collections. The syntax is
trivially same like xpath.
.. xpath()
To access value of an element we use the function xpath() :
. . data m atrix
There is no way to pass a default, when in doubt, use all switch to true, to ensure that when
a field is not there, it returns an empty list.
.. xelem()
To access an element using an xpath we use the function xelem() :
. datamatrix
A DataMatrix is an abstraction for a table with tuples of data as rows. Formally, then a data
matrix M = (C, R) where C a tuple of columns, and R a list of rows, such that ∀ri ∈ R , ri is a
tuple of size |ri | = |C|. In specificity, ri [Cj ] → rij , that is, the i’th rows j’th cell contains value
which is under column Cj .
In software such abstraction is natural for taking data out of data base tables, or from
spreadsheets, or comma or tab separated values file.
For example, suppose we have a tab delimited file “test.tsv” like this:
The field columns is a Set of string values, depicting the columns. rows, however are the list
of data values.
x = m.rows[0][1] // x is "Eve"
x = m.rows[1][2] // x is "Doe"
y = (m.c(1))[1] // y is "Eve"
y = (m.c(2))[1] // y is "Doe"
This tuple object is the one which gets returned as implicit row, when select operations on
matrices are performed.
But that is not all we want. We want to run SQL like select query, where we can specify
the columns to pass and appear. That is done using select() function. It takes the anonymous
parameter as an argument. The other normal arguments are objects which individually can be :
. An integer, the column index to project
. A String, the column name to project
. A range: [a : b] , the column indices to project, between a to b.
Thus :
x = m.select(0,2)
// x := [[1, Jackson], [2, Doe], [3, Johnson], [4, Smith]]
. . data m atrix
x = m.select(2 , 0 )
// x := [[Jackson, 1], [Doe, 2], [Johnson, 3], [Smith, 4]]
x = m.select(2 , 0 , where {
( $.Number > 2 ){
$[2] = $[2].toLowerCase()
}
} ) // x := [[johnson, 3], [smith, 4]]
One can create a matrix out of an existing one. Observe that, using project and select generates
only the list of data values, not a new matrix itself. If one wants to create a matrix out of an
existing one :
This generates a new matrix named m2, with columns starting from “Last Name” and “Number”.
The rows values are exactly the same as the select query done.
.. Keys
Given we have matrices (m1, m2) , we can try to figure out if they differ or not. A Classic
case is comparing two database tables from two different databases. A very naive code can be
written as such :
chapter . input and outpu t
def match_all_rows(m1,m2){
count = 0
for ( r1 : m1.rows ){
// linearise the left tuple
st1 = str(r1,’#’)
for ( r2 : m2.rows ){
// linearise the right tuple
st2 = str(r2,’#’)
// they match
if ( r1 == r2 ){ count += 1 }
}
}
( count == size(r1.rows) && count == size(r2.rows) )
}
The complexity of this algorithm, with respect to the size of the rows of the matrices is :
Θ(n2 ), given n is the rows size of the matrices. Given both the matrices have m columns, the
complexity would become Θ(n2 m2 ), a huge amount considering a typical (n, m) := (10000, 10).
We can obviously improve this setting:
def match_all_rows(m1,m2){
// exactly m * n
l1 = list(m1.rows ) as { str($.o,’#’) }
// exactly m * n
l2 = list(m2.rows ) as { str($.o,’#’) }
// exactly n
diff = l1 ^ l2
empty(diff) // return
}
The new complexity comes to Θ(nm). The thing we are implicitly using is known as key. We
are not really using the key, but, stating that key uniquely represents a row. The criterion for a
set of attributes to be qualified as key can be described as :
What happens when the keys are non unique? That boils down to the list, but then we
should be able to remember, which rows have the same keys, or rather what all rows are marked
by the same key. So, we may have a keys function, assuming keys are not unique :
. . data m atrix
This idea is already in-built in the DataMatrix, and is known as the keys() function :
So, to simply put, keys() generate a key dict for the matrix, as shown below, using the matrix
in (..) :
We can generate a non unique key too, just to show the practical application :
.. Aggregate
Once we establish that the keys function did not generate unique keys, one may want to
consolidate the rows to ensure that the key points to unique rows. This is done by the aggregate()
function. This function can be surmised to generate a new matrix from the old matrix, where
the same keys pointing to different rows must be aggregated based on columns.
chapter . input and outpu t
Environment is needed to run any system. In this chapter we discuss about that environment,
which is aptly called the Operating System. We would discuss about how to make calls to the
operating system, how to create threads, and how to operate in linear ways in case of error
happened, and how to have random shuffling generated.
status = system(command_to_os)
status = system(word1, word2,...)
The return status is the exit status of the command. It returns for success and non zero for
failure. For example, here is the sample :
In case of command is found, but command execution reported an error, it status would be
non zero:
chapter . interacting with environmen t
*/
In the same way, multiple words can be put into system, which is of some usefulness when
one needs to pass quoted arguments :
// the result of "ls -al ." is not shown at all, use popen()
status = system("ls" , "-al" , "." )
/* status code is 0 */
This way, one can redirect input, output and error. A call to popen() w/o any input yields a
dummy abstraction of the current running process, which then can be used to extract the pid by
:
i = 3
poll( 1000, 200 ) where { i-= 1 ; i == 0 }// induce a bit of wait
// this returns true : the operation did not timeout
i = 100
poll( 1000, 200 ) where { i+= 1 ; i == 0 } // induce a bit of wait
// this returns false : timeout happened
and, finally, we replace the while statement of the thread example using poll() :
t = thread() as {
/* do anything one needs */
}
/* java isAlive() maps to alive */
poll( 10000, 300 ) where { ! t.alive }
$count = 0
// create a list of threads to do something?
l = list( [0:10] ) as { thread() as { $count+= 1 } } // increase count...
poll ( 10000, 50 ) where {
running = select(l) where { $.o.isAlive } ; empty(running) }
println($count) // what is the value here?
chapter . interacting with environmen t
You would expect it to be , but it would be any number x ≤ 10, because of the threading
and non-atomic issue. To resolve it, put it inside an atomic block (#atomic{} ) :
$count = 0
// create a list of threads to do something?
l = list( [0:10] ) as { thread() as { #atomic{ $count+= 1 } } }
// above tries to increase count...
poll( 10000, 50 ) where {
running = select(l) as { $.isAlive } ; empty(running)
}
println($count) // the value is 10
x = 0
#atomic{
x = 2 + 2
}
#atomic{
x = 10
println(x) // prints 10
/* error, no variable as "p’, x should revert back to 4 */
t = p
}
println(x) // x is 4
m1 = 0
#(t,r) = #clock{
// load a large matrix, 0.2 million rows
m1 = matrix(’test.csv’, ’,’ , false)
0 // return 0
}
println(t) // writes the timing in seconds ( double )
println(r) // writes the result : 0
The clock block never throws an error. That is, if any error gets raised in the block inside,
the error gets assigned to the result variable.
Note that the timing would always be there, no matter what. Even in the case of an error,
the timing variable would be populated.
. h a ndling of errors
We toyed with the idea of try-catch, and then found that : Why Exception Handling is Bad? ;
GO does not have exception handling. And we came to believe it is OK not to have exceptions
in a language that has no business becoming a system design language. In a glue language like
ZoomBA, we should code for everything, and never ever eat up error like situations. For this
specific need, Asserts are in place. In particular - the multi part arguments and the return story
- should ensure that there is really no exceptional thing that happens. After all, trying to build
a fool-proof solution is never full-proof. And one of the return values should then be asserted.
error( args... ) where [ { /* body, when evaluates to true raise error */} ]
The function error() can have multiple modes, for example this is how one raise an error with a
message :
when condition would be true, error would raised. If the condition is false, error() returns
f alse. In case we want to execute complex logic, and guards it against failure:
#(a,b) = [ 0 , 1 , 2 ] // a = 0, b = 1
#(,a,b) = [ 0 , 1 , 2 ] // a = 1, b = 2
The idea is that one can assign a collection directly mapped to a tuple with proper variable
names, either from left or from right.
Note the quirky “,” before a in the nd assignment, it tells you that the splicing of the array
should happen from right, not from left.
chapter . interacting with environmen t
Observe the “?” before the last argument e, to tag it as the error container. Thus, the system
knows that the error needs to be filled into that specific variable. Thus, one can write almost
linear code like this:
The bye() function, when called, returns from the current calling function or script, with
the same array of argument it was passed with.
. o r d er and randomness
But then, the issue here is : the code iterates over the string once to generate the split array, and
then again over that array to generate the list of integers, selecting only when it is applicable.
Clearly then, a better approach would be to do it in a single pass, so :
s = ’11,12,31,34,78,90’
tmp = ’’
l = list()
for ( c : s ){
continue ( c == ’,’ ){
l += int(tmp)
tmp = ’’
}
tmp += c
}
l += int(tmp)
println(l)
However, this is a problem, because we are using too much coding. Real developers abhor
extra coding. The idea is to generate a state machine model based on lexer, in which case, the
best idea is to use the tokens() function :
. . o r d e r and randomness
h = hash(’abc’)
// h := 900150983cd24fb0d6963f7d28e17f72
It defaults to "MD", so :
They are the same. One can obviously change the algorithm used :
hash([algo-string , ] <string> )
There are these two specific algorithms that one can use to convert to and from base
encoding. They are “e” to encode and “d” to decode. Thus, to encode a string in the base
:
def compare( a, b ) {
if ( a < b ) return -1
if ( a > b ) return 1
return 0
}
// or in short, with sign function :
chapter . interacting with environmen t
Obviously, one needs to define the relation operator accordingly. For example, suppose we have
student objects :
And we need to compare these two. We can choose a particular field, say “id” to compare
these :
def compare( a, b ) {
if ( a.id < b.id ) return -1
if ( a.id > b.id ) return 1
return 0
}
def compare( a, b ) {
( a.id < b.id )
}
In ZoomBA compare functions can be of both the types. Either one choose to generate a
triplet of (−1, 0, 1), or one can simply return true when lef t < right and false otherwise. The
triplet is not precisely defined, that is, when the comparator returns an integer :
. The result < 0 =⇒ lef t < right
. The result = 0 =⇒ lef t = right
. The result > 0 =⇒ lef t > right
when it returns a boolean however, the code is :
cmp = comparator(a,b)
if ( cmp ) {
println( ’a<b’ )
}else{
println( ’a >= b’ )
}
In the subsequent section anonymous comparators would be used heavily. The basic syntax
of these functions are :
As always $.0 is the left argument (a) and $.1 is the right argument (b). Thus, one can define
the suitable comparator function to be used in order.
cards = [’B’,’C’,’A’,’D’ ]
sa = sorta(cards) // ascending
// sa := [A, B, C, D]
. . o r d e r and randomness
sd = sortd(cards) //descending
// sd := [D, C, B, A]
Now, sorting is anonymous block ( function ) ready, hence we can sort on specific attributes.
Suppose we want to sort a list of complex objects, like a Student with Name and Ids. And now
we are to sort based on "name" attribute:
.. Heap
A heap is an entirely different matter. Heap stores the ‘k’ largest or smallest entries in a
collection that is given to it. This structure let’s you find top ‘k’ or bottom ‘k’ as you wish. heap()
function in ZoomBA creates the Heap.
h[0] // 7
h[-1] // 9 : last element
And finally, one can always use a comparator to create the Heap, because that comparison is
needed to compare elements:
For example :
l = [10,1,23,12,15,0,3]
pq = pqueue()
for ( l ) { pq += $ } // consume
pq.peek // 0
while ( !empty(pq) ){ println(pq.poll) } // 0 1 3 10 12 15 23
l = [0,-1,-3,3,4,10 ]
s = sum(l)
/* sum := 9 */
Obviously, the function takes anonymous function as argument, thus, given we have :
x = [ 2,3,1,0,1,4,9,13]
s = sum(x) as { $.o * $.o }
/* 281 */
Thus, it would be very easy to define on what we need to sum over or min or max. Essentially
the anonymous function must define a scalar to transfer the object into.
x = [ 2,3,1,0,1,4,9,13]
#(m,M) = minmax(x)
// [0, 13]
random() function can be used in selecting a single value at random from a collection :
l = [0,1,2,3,4]
rs = random(l) // rs is a randomly selected element
It can also be used to select a random sub collection from a collection, we just need to pass
the number of elements we want from collection :
a = [0,1,2,3,4]
rs = random(a,3) // pick 3 items at random without replacement
// works on strings too....just to array
rs = random("abcdefghijklmpon".toCharArray ,10)
Given a seed data type, it generates the next random value from the same data type. Behold
the behaviour:
. e r r ors in zoomba
ZoomBA was written as a glue language. Therefore, utmost care is taken to report any error, in
a precise manner. For reference, we have used this file called t.zm for all the examples.
obj = { ’a’ : 10 }
println( obj.x ) // but there is no property called ’x’ !!!
Note that the error also tries to showcase what all properties the object has.
val = 10
func( val ) // where is this function?
It is simply stating that the function does not exist. Now, there can be issue with parameter
passing, as in below:
def func( x ){
println(x)
}
func() // parameter is not passed!
zoomba.lang.core.types.ZException$Function:
parameter ’x’ is not assigned!: in method : func :
--> /Codes/zoomba/image_scraper/t.zm at line 4, cols 1:6
|- func --> /Codes/zoomba/image_scraper/t.zm at line 4, cols 1:6
Wait a bit here. It is OK to have a function where we use implicit args @ARGS, but not ok to
have a parameter and then not using it.
Another related errr is method mismatch, which happens because ZoomBA is a dynamic
language:
s ="foobar"
s.foo()
Clearly, no such method foo() exists in the String class and hence, the error comes to be:
zoomba.lang.core.types.ZException$Function: foo : no such method! :
foo : in class : class java.lang.String :
--> /Codes/zoomba/image_scraper/t.zm at line 2, cols 3:7
|- foo --> /Codes/zoomba/image_scraper/t.zm at line 2, cols 3:7
What happens when a method exists, but the parameters do not match?
s ="foobar"
s.toString("boohahah")
Clearly, no such method toString(String ) exists in the String class and hence, the error comes
to be:
zoomba.lang.core.types.ZException$Function: toString : args did not match any known method! :
toString : in class : class java.lang.String :
--> /Codes/zoomba/image_scraper/t.zm at li
ne 2, cols 3:20
|- toString --> /Codes/zoomba/image_scraper/t.zm at line 2, cols 3:20
def funky(z){
println(y)
}
def func( x ){
funky( x )
}
func(42)
Even a for iterated loop comes under this scheme, that is because for-iterator is actually a
function, calling Java’s hasNext() and next() over an iterator.
chapter . interacting with environmen t
q = 0
p = 42
println( p/q )
12 = 42
this culminates in :
zoomba.lang.core.types.ZException$Variable:
Assignment not possible into ASTNumberLiteral :
--> /Codes/zoomba/image_scraper/t.zm at line 1, cols 1:2
def fact( n ){
if ( n == 1 ) return 1
n * fact( n ) // missed the : n - 1
}
fact(2)
x = null
println( x.method() ) // imagine there is actually something
. d e b ugging
x = 10
#bp // here is how we add a breakpoint as statement!
println(x)
when we run this with the interpreter ( or even programmatically ) this will happen:
============= With Power, Comes Responsibility ========
BreakPoint hit: --> /Codes/zoomba/image_scraper/t.zm at line 2, cols 1:3
============= Please use Power, Carefully..... ========
//h brings this help.
//h key_word : shows help about the keyword
//q quits REPL. In debug mode runs till next BreakPoint
//v shows variables.
//c In debug mode, clear this Breakpoint.
//r loads and runs a script from REPL.
Enjoy ZoomBA...(0.1-beta5-SNAPSHOT-2019-Feb-01 16:58)
(zoomba)
We are now in the debugger. In here, we can fire any expression, check any variables, upto this
point:
(zoomba)//v
context : {
x => 42 /* class java.lang.Integer */
}
when we run this with the interpreter ( or even programmatically ) this will happen: Syntax of
the conditional break point is #bp [condition] .
chapter 10
O b j e c t O r i e n tation
Objects are essential, programmatically. It can be argued that a map or a dictionary can work
perfectly well instead of “fully blown” objects in multitude of languages.
The idea of hiding states is bad, because it assumes the incompetency of a programmer,
rather all of them. A philosophy which assumes philosophers are incompetent is contradictory
onto itself.
However, that is a philosophical debate, and is not needed to be played down. There is no
denial of facts, that modern computing world is object oriented. Thus, ZoomBA has objects,
even if we never intended it to have “language specific” objects in ZoomBA.
. i n troduction to classes
def Hello{}
creates a class called Hello. Obviously one needs to create a class, that is created by the new()
function :
That is all it takes to create classes in ZoomBA. We sincerely thought about the Pythonic way of
resolving class name as the creation of new classes, and then decided to go against it. In the
pythonic way, it becomes impossible to tell, whether or not this is a class constructor call, or
rather a generic function call. Observe this :
chapter . object orientatio n
and this is why we do believe the Pythonic way is ambiguous. The ZoomBA tenet of
minimisation of code footprint must also abide by the tenet of maintainability. Moreover,
ZoomBA is prototype based, hence none of the ZoomBA objects are actually a JVM class objects.
They are, in the truest sense, dictionaries holding functions and properties.
Every field is public, and we are Pythonic. As the proverb goes “Never design a tool for fools,
while you were designing version ., the fools upgraded themselves to version .” . The Java naked
field access without any getter and setter is . times faster than that of the getters and setters.
But we all agree that is a bad way of randomly assigning properties to an object. Thus, we
want to somewhat put properties inside the object in the time of creation.
One easy way out is as follows:
One should also note that, both normal and string literals are allowed, much like JavaScript.
This is the easiest way to create a prototype based object. In the runtime, we can actually change
the values, if we desire:
But this is not what we really want. We want something which can programmatically
initialize the instance, while it is being prepared. That is next.
. . i n t r oduction to classes
.. Constructor
This brings to us the notion of constructor. There is no constructor in ZoomBA, nothing actually
constructs the class. There is obviously an instance initialiser. This is defined as such :
def Hello{
def $$(){
$.greet_string = ’Hello, World!’
}
}
h = new ( Hello )
println( h.greet_string )
Now, this $$() is a specific function, which is the instance initialiser. The special identifier $
in the context in a class also is a specific identifier, identifying the instance of the class being
passed, this is pythons self. One obviously can pass arguments to the constructor :
def Hello{
def $$(greetings=’Hello, World’){
$.greet_string = greetings
}
}
// the ordinary folks
h1 = new ( Hello )
println( h1.greet_string )
// from the Penguins of Madagascar
h2 = new ( Hello , "Hey, Higher Mammal! Do you read?" )
println( h2.greet_string )
This brings the question of how do we encapsulate the state of the object. Sometimes, you do not
want to let people access stuff. You want to handle the properties of an instance yourself. Those
are done with the instance methods, constructor method is one special type of instance method:
def Hello{
def $$(greetings=’Hello, World’){
$.greet_string = greetings
}
def greet(){
println( $.greet_string )
}
}
h1 = new ( Hello )
h1.greet()
def Hello{
def $$(greetings=’Hello, World’){
$.greet_string = greetings
}
def greet(person="Everyone"){
println( ’To @%s : %s\n’, person , $.greet_string )
}
}
h1 = new ( Hello )
h1.greet("ZoomBA")
To have class level properties and methods, which gets initialised only once, we must have static
constructs, which are indistinguishable from prototype.
These are class level fields, and can not be accessed using instances. This can be accessed by
class level accessors :
def MyClass{}
MyClass.foo = "bar"
println( MyClass.foo )
mc = new ( MyClass )
mc.foo // "bar"
. o p erators
ZoomBA supports operator overloading. The following operators and functions can be over-
loaded :
. . o p e r ators
operator method
toString $str
hashCode $hc
equals $eq
compareTo $cmp
+ $add
- $sub
* $mult
/ $div
+= $add$
-= $sub$
*= $mult$
/= $div$
** $pow
unary - $neg
| $or
& $and
^ $xor
|= $or$
&= $and$
^= $xor$
As an example, we present the class Complex number, which overloads lots of operators :
chapter . object orientatio n
def Complex {
def $$ (x=0.0,y=0.0){
$.x = Q(x)
$.y = Q(y)
}
def $str(){
str(’(%f,i %f)’, $.x, $.y)
}
def $eq(o){
( $.x == o.x && $.y == o.y )
}
def $hc(){
31 * $.x.hashCode() + $.y.hashCode()
}
def $neg(){
new (Complex , -$.x , -$.y)
}
def $add(o){
new (Complex , $.x + o.x , $.y + o.y )
}
def $sub(o){
new (Complex , $.x - o.x , $.y - o.y )
}
def $mul(o){
new (Complex , $.x * o.x - $.y * o.y , $.x * o.y + $.y * o.x )
}
def $div(o){
mod = ( o.x **2 + o.y **2 )
x = ( $.x * o.x + $.y * o.y )/mod
y = ( $.y * o.x - $.x * o.y )/mod
new (Complex , x, y )
}
}
// test it out :
c0 = new (Complex )
c12 = new (Complex , 1, 2)
c21 = new (Complex , 2, 1)
// test some?
println( -c12 )
println ( c0 == -c0 )
chapter 11
P r ac t i c a l Z o omBA
Examples should be abundant, and this chapter is massively inspired from Programming
Pearls. The idea behind this chapter is many examples that happens practically, and how to
solve the problems in least possible code, in the best possible way. Although the whole book is
pretty abundant in examples - this chapter has its own advantages.
. a note in style
Standards are for humans, and not otherwise. Being said that : A guideline is a guideline
and is not a law of a country or nature that one abides by. Being said that, it is more of the
matter of taste, readability, and understandability and something called reproducibility of a
bug. Therefore the notion of test flow is of utmost importance. Test cases should fail when app
failed, and should pass only when app is working. This tenet drives the guidelines of coding.
Therefore, the following subsections are for both the author and the reviewer.
.. Comments
Comments are not after thought. For any standard library use, comment why and what we are
trying to accomplish. ZoomBA has a minimalistic syntax, so it is better to explain why we are
doing what we are doing. This is a NO GO :
assert ( size( @ARGS ) >= 2 , ’Problem : Args are less than 2!’ )
#(some_param, some_other) = @ARGS // storing the args into two params as tuple
chapter . practical zoom ba
One can use caml casing or can use underscore, but mixing them in a single file produces
readability problem. So, please do not use it. thisIsABigName and this_is_a_big_name both are
acceptable, as long as they are not in the same file. But, iamamoron is not acceptable as any
name.
When in doubt, please use expanded names rather than the cryptic ones. Don’t be shy of
using variables, because variable assignment and get value is fast. By design, it is not possible
to write a ZoomBA code beyond lines, unless you are using it for System design, which you
should not.
.. Assertions
Raise assertions, and raise it fast. ZoomBA is a dynamically typed, declarative language. It is of
utmost important to check for errors as soon as it happens, and try not to manage pass it. That
means :
def some_brilliant_function(x=42,y=false){
assert( x isa ’int’ , ’Wrong type of x : needs int’ )
assert( y isa ’bool’ , ’Wrong type of y : needs bool’ )
//... now proceed...
}
This should not be taken as The Type Debate but a way to mitigate what is expected. In the
same way, this paves the way for :
if we sort the all the words in a dictionary letter by letter and then use that sorted word as a key?
Then we can easily solve the problem by sorting on the letters of the word given and then checking if
that as key exist in the dictionary, then, find all possible matches !
word_dict = mset(file(’words.txt’)) as {
// generate char array
ca = $.o.toCharArray()
// generate a key by sorting and concatenating
key = str(sorta(ca),’’)
}
// now a jumbled up word exists ?
ca = jumbled_word.toCharArray()
key = str( sorta( ca ) , ’’)
matches = word_dict[key] ?? [] // in case key is not found?
println(matches)
and this solves the problem. Notice that many advanced concepts are being used in the solution.
A related question is this :
Given a list of strings, return a list of lists of strings that groups all anagrams.
Ex. given [ trees, bike, cars, steer, arcs ]
return [ [cars, arcs] , [bike], [trees, steer] ]
To solve the problem, use the same approach :
chapter . practical zoom ba
Given a list of integers, and a value sum, determine if there is a sublist of the given list with sum
equal to given sum. There is a dynamic programming solution - which is left for the reader to
figure out. Our solution would be minimalist. Observe that the solution is finding all possible
combinations of the list, and check where the sum comes up. So, we notice that the previous
anagram problem is the building block of this problem too! So, suppose the list of integers are
stored in li :
Observe that, the most generic way to represent all of the above problems is by introducing a
predicate P ($) , and stating the problem as such :
Given a list , and a predicate P ($), determine if there is a sublist of the given list where P ($) is True.
AS = sorta(A)
BS = sorta(B)
e = 0.01 // say?
s = #|AS| // the size
close = !exists( [0:s] ) where { #| AS[$.o] - BS[$.o] | > e }
word = ’ABCDAABCD’
m = mset(word.toCharArray() )
#(min,Max) = minmax(m) where { size($.l.value) < size($.r.value) }
solvable = ( #|word| + 1 < 2 * #|Max.value| )
! ( solvable ) || bye(’sorry, no solution exists!’)
keys = list(m.keySet())
shuffle(keys)
len = #|keys|
result = fold ([0:#|word|],’’) as {
i = $.index
key = keys[i % len]
$.partial + m[key][0]
}
println(result)
Observe the use of the bye() function. When it is called, it simply returns from the calling
function, with the string as return value. Wonder where it can be practically applied? We can
think of one. Suppose we are going to go at a diner where girls and boys came. Now, there
would be ( if the soap operas are slightly true ) boys and girls who were involved with one
another. Thus a relationship existed is defined by x EXy which means x, y were emotionally
attached. Now, an extended relationship can be found, extending R, so that x EXy and y EXz
implies x EXz where N stands for do not talk. Clearly, N is a set generate by EX. Clearly none
in the same N set wants to talk to one another, and thus do not want to seat side by side.
That brings the problem, and now you see, how emotional stuff can generate a nice computer
algorithm, that Microsoft asks in Interviews.
def sum(x,max){
dif = max - x
x*(diff + 1) + diff*(diff+1)/2
}
This function uses integer arithmetic, and hence is recursive. Add and subtract are actually
recursion. Lets solve it in another way :
def sum_func(x,max){
if ( max == x ) return x
return ( max + sum_func(x, max - 1 ) )
}
size( @ARGS ) > 1 || bye(’I need two arguments!’)
#( x , max ) = [ int(@ARGS[0]) , int( @ARGS[1] ) ]
println (sum_func(x,max))
def to_str(i){
if ( i == 0 ) return "0"
sign_val = (i < 0 )?’-’:’’ // same here, not using standard
i = ( i> 0 )?i:-i // not using any standard function at all
s = ’’
for ( ; i > 0 ; i /= 10 ){
s = str( i % 10) + s
}
sign_val + s
}
println ( to_str( int(@ARGS[0] ) ))
n : 1243
Indices:
(0,3)
(2,3)
Answer:
3421, 3214, 4213, 4231,...
The result is 4231 which is the largest.
The problem is that of Permutation Graph. Observe, then, the rules lets you generate nodes
from the starting node. Also observe that the size of the graph generated by the rules is finite,
the graph generated is strictly a subgraph of the overall permutation graph, which is finite by
definition. A subset of a finite set is finite.
So, how to generate the graph? Obviously we need to apply the rules again and again. But
there is a chance of a cycle.
chapter . practical zoom ba
The problem here, is a very well known problem: Graph Exploration. Given this problem
came from Facebook is quite apt. So, how do we solve it?
. Keep a dictionary of traversed nodes, or say integers : nodes := { node : traversed ? false }
. Initialise nodes to starting seed ().
. Pick one item from the node set, which is not traversed, and apply both the rules.
. If the results generates new nodes, not already in nodes, then add them to nodes
. If traversal yield old nodes, mark the node as traversed : true
. check if there is any nodes left to pick and traverse
. The exploration is done, now go over all the nodes and check which one is the largest.
Thus, to solve it :
a very simple solution would be to find all possible substrings and see which one is the highest
size. The base case is finding if the given string has no duplicates. Hence the code :
s = ’abacdefghabk’
def vals: { large_size : 1 , substring : ’a’ } // create a container
indices = [0:#|s|].list()
result = join ( indices, indices ) where {
continue($.0 >= $.1)
ss = s[$.0 : $.1]
l = size(ss)
continue(large_size > l )
set_size = #|set(ss.toCharArray())|
continue( set_size != l )
vals.substring = ss
vals.large_size = l
false
}
println(’%s with size %d\n’, vals.substring, vals.large_size )
l = [ 6,7, 8, 1, 2, 3, 9, 10 ]
i = [0:#|l|].list()
ans = l[0] * l[1] * l[2]
result = [l[0],l[1],l[2] ]
join(i,i,i) where {
// we need subsequences so...
continue( $.0 >= $.1 or $.1 >= $.2 )
// we need ascending, so
continue( l[$.0] > l[$.1] or l[$.1] > l[$.2] )
r = l[$.0] * l[$.1] * l[$.2]
continue(r <= ans)
result = [l[$.0], l[$.1], l[$.2] ]
ans = r ; false
}
printf(’Values %s with product %d %n’, str(result) , ans )
In this form, we can generalise it, to any number of items instead of . Suppose the same
problem was asked and the size of the tuple was fixed at m. The function, instead of making
it a multiply, one can generalise to any f ($). The reader should code that general problem,
accordingly.
d = [1, 2, 7, 8, 9]
l = ’1278’
r = ’9’
min = int(l) + int(r)
// observe that a partitions are done using bits
n = #|d|
upto = 2 ** (n+1)
lfold [1:upto] as {
selection = $.o
left = list() ; right = list()
bitmask = 1
lfold( [ 0 : n ] , bitmask ) as {
bitmask = $.o
// bitwise and operation
b = selection & bitmask
if ( b == 0 ){
left += d[ $.index ]
} else {
right += d[ $.index ]
}
bitmask = bitmask * 2
}
// obvious
continue ( empty(left) || empty(right) )
// if any starts with 0
continue ( left #^ 0 || right ^# 0 )
// due stuff
left = str(sorta(left),’’) ; right = str(sorta(right),’’)
v = Z(left) + Z(right)
continue ( v >= min )
min = v ; l = left ; r = right
}
printf(’%s + %s = %d\n’ , l, r, min )
Given a number N , write a program that returns all possible combinations of numbers that add
up to N , as lists. (Exclude the N+=N) . For example, if N = 4 return [[1, 1, 1, 1], [1, 1, 2], [2, 2], [1, 3]]
See more about the problem here. One of Indian greats, rather worlds finest who worked beyond
this problem.
To systematically generate the partitions, observe that one needs to select groups of “”s
from the list of N “” :
[[1], [1], [1], [1]] := [[1], [1, 1, 1]] := [[1, 1], [1, 1]] := [[1], [1], [1, 1]]
Thus, the problem is selecting the gaps between these “”s, and there are N − 1 of them.
Thus we must systematically select between [1 : N ] gaps, numbered between 0 and N − 1. Sum
the resulting groups, and keep them sorted as the key. To select a number between 0 and N − 1
use the binary encoding of numbers between 1 to 2N −1 .
chapter . practical zoom ba
def ramanujan_partition(N){
// imagine N-1 + symbols
// 1+1+...+1
// select + symbols
max = 2 ** ( N - 1 )
partitions = set()
for ( n : [1: max] ) { // avoid trivial partition
s_rep = str(n,2)
s_rep = ’0’ ** ( N -1 - #|s_rep| ) + s_rep
// in s_rep, ’1’ means select partition from that ’+’ symbol
// for N = 5, 1 + 1 + 1 + 1 + 1
// 0001 means 0 0 0 1 => 4 + 1
current_partitions = list()
cur_val = 1
for ( c : s_rep ){
continue ( c == _’1’ ){
current_partitions += cur_val
cur_val = 1
}
cur_val += 1
}
current_partitions += cur_val
sorta( current_partitions )
partitions += str(current_partitions )
}
partitions // return
}
println( ramanujan_partition(5))
items = [3,4,5,9,2,1, 3]
d = mset(items)
lfold ( d ) as { d[$.key] = [ size($.value) , 0 ] }
keys = list(d.keySet())
keys = sorta(keys)
higher_count = 0
rfold(keys) as {
t = d[$.o]
t[1] = higher_count
higher_count += t[0]
}
result = list(items) as { (d[$.o])[1] }
println(result)
d = [1,2,3,4]
p = join ( @ARGS = list( [0:size(d)] ) as { d } ) where {
d == $.o } as { int( str($.o,’’) ) }
S = sum(p)
println(S)
The result can be found algebraically. Observe that the summation would be over the digits
1, 2, 3, 4 and the integers created would have the power places as 103 + 102 + 10 + 1 = 1111, so,
each for every permutation starting with digit d would sum up to 1111 × (1 + 2 + 3 + 4) = 11110.
Now, how many permutations stating with a digit d ? That would be found by taking that digit
out of the permutation, and permutating the rest of the digits : (4 − 1)! = 3! = 6, hence the total
would be 66660.
string_val = @ARGS[0]
nu = int( string_val )
num_arr = string_val .toCharArray()
r = [0 : #| string_val | ]
// pick the highest one
nhp = int ( str( sortd(num_arr) ,’’) )
nhp != nu || bye(’Dude, got to be kidding me!’)
join (r,r) where {
i = $.0 ; j = $.1
continue( i >= j )
num_arr = string_val.toCharArray()
t = num_arr[i]
num_arr[i] = num_arr[j]
num_arr[j] = t
nn = int ( str(num_arr,’’))
continue( nn < nu )
if ( nn < nhp ){ nhp = nn }
false
}
println(nhp)
Now, this works, so, question is, can we make it any faster? Turns out, we can. Given next
higher permutation is possible, there has to be one discrepancy where Di < Dj with i < j. The
objective would be to find minimum of such a span |i − j| is minimum, with maximum value of i.
So, we can start from the right, with i = n − 2 and j = n − 1, and then searching left for a match.
def next_higher_perm( n ){
digits = str(n).toCharArray()
len = #|digits|
j = len - 1
while ( j > 0 ){
i = j - 1
while ( i >= 0 ){
if ( digits[i] < digits[j] ){
// swap and we are done
t = digits[i]
digits[i] = digits[j]
digits[j] = t
return int( str(digits,’’) )
}
i -= 1
}
j -= 1
}
n // nothing can be done so...
}
println ( next_higher_perm( @ARGS[0] ) )
Given a string S , print the longest substring P such that lexicographically : P > S. One may
assume that such substring exists.
. . a s s o rted theoretical examples
S = "hello , world"
def max_large_sub(s){
len = #|s|
j = index([1:len]) where { s[0] < s[$.o] }
j > 0 ? s[0:j+1] : ’’
}
println(max_large_sub(S))
s = @ARGS[0]
r = [0 : #|s|]
partitions = join( r, r ) where {
i = $.0 ; j = $.1
// do not consider trivial one chars
continue( i >= j )
ss = s[ i : j ]
// replace the join with reassignment of item
( (ss ** -1) == ss )
} as { s[$.0:$.1] }
println(partitions)
Count triplets in a collection with sum smaller than a given value. One must read the theory
behind here. Without much ado, one can solve this problem by using the joins :
Find if there exists Pythagorean Triplets in an array. That is, given an array of integers, write a
function that returns true if there is a triplet (a, b, c) that satisfies a2 + b2 = c2 . In another word,
find all of them. This is what we find here.
chapter . practical zoom ba
def perm_exists(s1,s2){
small = s1 ; large = s2
if ( #|s1| > #|s2| ){ small = s2 ; large = s1 }
small_arr = small.toCharArray()
small_set = set(small_arr)
large_len = #|large| ; small_len = #|small|
i = 0
while( i < large_len ){
if ( i + small_len > large_len ) { return false }
continue( large[i] !@ small_set ){ i += 1 }
ss = large[ i : i + small_len - 1]
if ( ss.toCharArray() == small_arr ){ return true }
i += 1
}
return false
}
def pivot_partition(a){
ma = mset(a)
ma = dict(ma) as { [ $.key , size($.value)] }
keys = list( ma.keySet() )
keys = sortd(keys)
greater_eq = 0
i = index(keys) where {
greater_eq += ma[$.o]
greater_eq >= $.o
}
( i > 0 ) ? keys[i] : ’None’
}
. . a s s o rted practical examples
x = 0
fold ( [0:10] ) as {
x+= 1
println(x) // goes from 0,1,2,... 9
}
println("But x is... " + x ) // prints 0
One can see that the variable ’x’ was not eventually modified. But repeated iteration of the
fold will preserve the state of the variable.
that is, lN ⊂ 2l where 2l is the power list(set) of list l. We use the same idea as Ramanujan
Partition, but this time use the index set as the bitmap . We also generalise the notion of sum
using a generic predicate:
This section contains examples which would probably never be asked in any interviews, if
anyone asks the in an interview, please join that firm immediately.
Thus, the only way to handle these sort of thing would be using the projection operator. That
is, isolate the set of fields which are equivalent , that would be a list of tuple : Ci = (O Ci ,N Ci )
where O Ci is the old field, while N Ci is the new field. That is really not the most generic way,
but let’s assume it is. Now, it boils down to doing the projection on these :
to = πO C (Oo )
and
tn = πN C (On )
and now, there are lists of it. Thus, now, these two list should be equal. Hence, in ZoomBA, here
is the code one would write :
and this F ⊆ C. The question is how to test for filtering? Given we already have a predicate P ()
defined :
But this has a problem. The solution loops over both F and C. Will there be a way to reduce the
looping? Given C, F are sets, it is easy :
s = "13413124"
d = lfold (s.toCharArray(),dict()) as {
k = int($.o)
if ( k !@ $.p ){ $.p[k] = list() }
$.p[k] += $.i // store the index
}
println(d)
You are given a large set of integers, which are not sorted. Figure out a method to retrieve the
largest N elements, in Θ(n) run time. For this, we use heap() .
N = 10
l = list([0:100]) as { random(1000) }
// create a max-heap
h = heap(N,false)
lfold (l) as {
h+= $.o
}
println(h)
To create a min-heap, just use the argument as false to heap() function. The first argument is the
heap size.
Write a function which, given two integers (a numerator and a denominator), prints the decimal
representation of the rational number “numerator/denominator”. Since all rational numbers
end with a repeating section, print the repeating section of digits inside parentheses; the
decimal printout will be/must be. Examples: (1, 3) = 0.(3) , (2, 4) = 0.5(0) ; (22, 7) = 3.(142857).
chapter . practical zoom ba
def rational(n,d ){
// these are stored in list
digits = list()
// in case there is a loop/recurrence
visited = dict()
// find the integer part
int_part = str(n/d )
// rest is the new numerator
n %= d
// index is there to find the spot for each : n
// where "(" is needed to be inserted
i = 0
while(n != 0 ){
n = n * 10
while ( n < d ){
// append 0, only when
digits += ’0’
n *= 10
i+= 1
}
break ( n @ visited ){
// found a recurrence
digits.add( visited[n] , ’(’)
digits += ’)’
}
// mark the visit of n
visited[n] = i
// add to the digits
digits += (n/d)
n %= d
i+= 1
}
str (’%s.%s’, int_part , str(digits,’’) )
}
print ( rational(1,2) )
l = [2,4,6,9,5,1]
n = size(l)
result = rfold (l,list()) as {
cur_index = n - $.i - 1
continue(cur_index == 0 )
v = $.o
#(m,M) = minmax([0:cur_index]) where { l[$.0] < l[$.1] }
if ( l[m] < v ){
$.p.add(0, cur_index - m )
}else{
$.p.add(0,-1)
}
$.p
}
// for the first element
result.add(0,-1)
println(result)
. . a s s o rted practical examples
i_a = [0:#|A|]
i_b = [0:#|B|]
inversions = join (i_a,i_b) where {
$.0 < $.1 && A[$.0] > B[$.1]
}
chapter . practical zoom ba
Given two binary numbers each represented as a string write a method that sums up the
binary numbers and returns a result in the form of binary number represented as a string.
One may assume that input fits in the memory and the input strings are, in general, of dif-
ferent length. Optimize your solution, do not use unnecessary ‘if’ branching. As an example:
sumBinary(’’, ’’) returns 1001010.
In ZoomBA, the solution would be cheating:
def sumBinary(a,b){
i = int(a,2,null) // specify base 2, and default
j = int(b,2,null)
x = i + j
str(x,2)
}
Which works out. However, purists would still argue that we cheated, and did not actually sum
it up bit by bit. So, to appease them, as Amazon ordered :
. . a s s o rted practical examples
// ABC : RC
bin_add_logic = { ’000’ : ’00’ ,
’010’ : ’10’ ,
’100’ : ’10’ ,
’110’ : ’01’ ,
’001’ : ’10’ ,
’011’ : ’01’ ,
’101’ : ’01’ ,
’111’ : ’11’ }
// when carry is 0 append empty...else...1
carry_string = { ’0’ :’’ , ’1’ : ’1’ }
def bin_add(a,b){
size_a = #|a|
size_b = #|b|
#(m,M) = minmax(size_a,size_b)
// pad both of them up to max : M with 0
a = ’0’ ** (M - size_a) + a
b = ’0’ ** (M - size_b) + b
carry = ’0’
tot = fold ( [M-1:-1] , ’’ ) as {
key = a[$.o] + b[$.o] + carry
add_result = bin_add_logic[key]
carry = str(add_result[1])
$.p = add_result[0] + $.p
}
tot = carry_string[carry] + tot
}
#(a,b) = @ARGS
println ( bin_add(a,b) )
data = lines(’log_file.txt’,true)
data = list(data) as { float( $.o ) }
#(m,M) = minmax(data)
print(’Min : %f , Max : %f\n’, m, M )
bucket_size = 0.001
stat = mset(data) as { int ( ($.o - m )/ bucket_size ) }
keys = list ( stat.keySet() )
keys = sorta( keys )
T = float(size(data))
print(’Bucket\tTiming’)
lfold(keys) as {
printf(’%f\t%f%n’ , m + ($.o + 1) * bucket_size ,
size(stat[$.o]) * 100.0 / T )
}
chapter . practical zoom ba
If I am designing a media player and I want to store songs and play them in random order,
how will select the next song to play in a way which prevents the same song being played in
consecutive turn?
Print series 010203040506... using multi-threading. st thread will print only nd thread will
print only even numbers and rd thread print only odd numbers. Here is how we do it:
Suppose, we are trying to call a web api, which returns large chunks of data, and we can not call
it synchronously. Therefore, we need to ensure that after the method was successfully executed,
there is a callback method that should get called. This problem is trivial in ZoomBA and here is
how to get it done :
. . a s s o rted practical examples
Suppose, the problem is that of generation of queries. As an example, take this query :
Find all the customers who spent > minutes on Page "XYZ" & purchased > items of coffee_X
& gave a review of >.
The POJOs given are :
class PageView {
private String URL;
private String customerID;
private Integer timeSpent;}
class Purchase {
private String productID;
private String customerID;
private Integer itemsPurchased;}
class Review {
private String productID;
private String customerID;
private Integer reviewPoints;}
page_url = pages[’XYZ’]
customer_ids_pv = set (page_views) as {
continue ( ! ( $.URL == page_url &&
$.timeSpent > 2 ))
$.customerID }
product_id = products[’coffee_X’]
customer_ids_pur = set(purchases) as {
continue( ! ( $.customerID @ customer_ids_pv &&
$.productID == product_id &&
$.itemsPurchased > 2 ))
$.customerID }
customers = set (reviews) {
continue( ! ( $.customerID @ customer_ids_pur &&
$.productID == product_id &&
$.reviewPoints > 3 ))
$.customerID }
lfold(customers) as { println( $.o ) }
def generate_strings(template){
ta = template.toCharArray()
n = sum (ta , 0) as { ( $.o == ’?’ ) ? 1 : 0 }
p = 2**n
lfold ([0:p]) as {
s = str($.o, 2)
s = ’0’ ** ( n - #|s| ) + s
count = 0
r = lfold( ta, ’’) as {
if ( $.o == ’?’ ){
$.p += s[count]
count += 1
}else{
$.p += $.o
}
$.p
}
println(r)
}
}
The use of such a program is obvious for anyone who wants to make a finite decision game.
existing = set()
i = index ( file( ’urls.txt’ ) ) where {
u = $.o
continue( u !@ existing ){ existing += u }
true }
println( urls[i] )
You are given four integers ‘a’, ‘b’, ‘y’ and ‘x’, where ‘x’ can only be either zero or one. Your task
is as follows: If ‘x’ is zero assign value ‘a’ to the variable ’y’, if ‘x’ is one assign value ‘b’ to the
variable ’y’. You are not allowed to use any conditional operator (including ternary operator).
Follow up: Solve the problem without utilising arithmetic operators ‘+ - * /’.
This is one of the founding stone of the declarative paradigm, therefore, it qualifies as a
practical problem. The solution, of course can be done using what we explained in chapter :
conditionals = { 0 : a , 1 : b }
y = conditionals[x]
But, observe that hash is complex structure, we can simplify the problem by :
conditionals = [a, b ]
y = conditionals[x]
Suppose there is a parking with slots named as 1, 2, ..., n with a free slot designated as 0. How
can one rearrange the cars from a source configuration to a target configuration? The problem
is that every swapping of the cars must be via the empty slot. For example, if src is [1, 3, 0, 2, 4],
then to move a car 1 to the position of 3, one must go through the 0’th slot :
Given two strings, print all the inter leavings of the two strings. Interleaving means that
the if B comes after A , it should also come after A in the interleaved string. As an example,
with AB and CD we have : [ABCD, ACBD, ACDB, CABD, CADB, CDAB]. This has application :
DNA cross and sequencing. The solution is clearly non optimal - and must use grammar and
language generators to generate the specific strings in question, but the solution displays the
awesomeness of ZoomBA, so it is presented here.
. . a s s o rted practical examples
def in_order(s,arr){
len = #|s|
j = lfold(arr, 0 ){
break( $.p == len )
continue( s[$.p] != $.c[ $.i ] )
$.p +=1
}
(j == len)
}
s1 = "AB" ; s2 = "CD"
tot = s1 + s2
arr = tot.toCharArray()
arg = list{ arr }( [0 : #|arr| ] )
results = join ( @ARGS = arg ) where {
( in_order(s1,$.o) && in_order(s2,$.o)
} as { str($.o,’’)}
println(results)
A better strategy would be thinking about what interleaving would mean, and that brings
to the newer problem of finding all possible partitions of a string. What is a partition of a
string? Given a string S, a partition of size k is a k-tuple of substrings < s1 , s2 , ..., sk > such that
: S = s1 s2 ...sk , and none of the si is empty string. Thus, given a string aba, possible partitions
would be : [a, b, a], [ab, a], [a, ba]. This is very close the Ramanujan Partition Problem, and the
code to solve it shows why :
def partition_string(s,bi){
len = #|s|
def seed : { start : 0, l : list() }
seed = lfold ([1:len] , seed ) as {
if ( bi[$.o -1] == ’1’ ){
$.p.l += s[start : $.o-1]
$.p.start = $.o
}
$.p
}
l = seed.l
l += s[start:len-1]
}
def generate_partitions(s){
N = #|s|
n = 2 ** (N - 1)
p = lfold ([1:n] , list() ) as {
bi = str($.o, 2)
bi = ’0’ ** ( N - #|bi| - 1 ) + bi
$.p.add( partition_string(s,bi) )
$.p
}
}
The function generate_partitions generates partitions for a string. Now, how this problem is
related to interleaving problem? Start with a partition p1 from a string S1 . This partition
suppose has n1 substrings. Thus, there are n1 gaps available to interleave partitions from the
second string S2 .
Start with an example : given string abc and partition [a, b, c], and any other string to
interleave, we can choose to exploit the inner positions shown as ∗ as follows:
∗a ∗ b ∗ c∗
chapter . practical zoom ba
|n1 − n2 | < 2
and this, is the join condition! After this, what we need to do? We know, if the lengths differ,
then we need to pick the smaller, and fit that into the larger partition. We will call this function
insert(large,small). Now, if the sizes are the same, there are two different partitions from strings
“abc” and “ABC” :
AaBbCc and aAbBcC
and we would call it a function place(left,right). Thus, how these functions look?
They should not really be functions, but then it introduces the awesome notion called readability.
That is not entirely fad, there are meaning to the madness. Observe now, thanks to all these
modularisation of the source code, the final, interleaving code is now a piece of cake, or rather
very close to what is one:
def interleave(s1,s2){
partition_1 = generate_partitions(s1)
partition_2 = generate_partitions(s2)
join (partition_1,partition_2) where {
#(p1,p2) = $.o // assign
len1 = #|p1| ; len2 = #|p2|
continue( #|len1 - len2 | > 1 )
if ( len1 < len2 ){
println ( insert(len2, len1 ) )
} else if ( len2 < len1 ){
println ( insert(len1, len2 ) )
}else {
println ( place(p1,p2 ) )
println ( place(p2,p1 ) )
}
false // do not add them at all!
}
}
JVM is the powering force behind ZoomBA. This chapter illustrates various usage scenarios
where ZoomBA can be embedded inside a plain Java Program.
. h o w to embed
.. Dependencies
<dependency>
<groupId>org.zoomba-lang</groupId>
<artifactId>zoomba.lang.core</artifactId>
<version>0.1-beta5-SNAPSHOT</version>
</dependency>
ZoomBA has a random access memory model. The script interpreter is generally single threaded,
but each thread is given its own engine to avoid the interpreter problem of GIL.
The memory comes in two varieties, default registers, which are used for global variables.
There is also this purely abstract ZContext type, which abstracts how local storage gets used.
This design makes pretty neat to integrate with any other JVM language, specifically Java.
Here is an example embedding.
chapter . java connectivi t y
. p r ogramming zoomba
. e x tending zoomba
There are two extension points for Java developers who are going to make their classes and
functions ZoomBA ready.
[] Alfred V. Aho, Monica S. Lam, Ravi Shethi , Jeffery D. Ullman, Compilers: Principles,
Techniques, and Tools . Prentice Hall, .
[] Kanglin Li, Mengqi Wu, Sybex, Effective Software Test Automation: Developing an Automated
Software Testing Tool . Sybex , .
[] Guido van Rossum, Fred L. Drake, Python Language Reference Manual PythonLabs .
[] Martin Odersky, The Scala Language Specification Version . PROGRAMMING METHODS
LABORATORY EPFL, SWITZERLAND, .
bibliograph y
Index
ind e x
ZoomBA : philosophy,
ZoomBA : repl,
ZoomBA : tenets,