- Quick Introdution
- Variable Types
- Composite Types
- Declaring Variables
- List of Arrays
- Array of Lists
- Enums
- Functions
- Control Flow
- Classes
- Comments
- Concurrency
- Modules
- Coyote SQL
- File IO
- Networking
Welcome to Coyote - a fast, lightweight language designed for data engineers in mind. It lets you use the best features of both Functional and Object-Oriented languages while having a full-feature embedded SQL engine. The philosophy of the Coyote language is to incorporate the power of a full-fledged language with built-in SQL databases and OLAP stores so that the tight integration between both produces a seamless experience that adds power to Data Science and Data Analytics.
Unlike other scripting languages, Coyote makes you declare your variables and provides the security that comes from compile-time type checking. At the same time, it offers extensive data manipulation structures that can be combined as needed. Multi-dimensional arrays can contain classes and functions, functions can be stored in Data Frames, and all objects can be passed as parameters to functions or methods.
To get started, you may download the latest version from here
Type | Description |
---|---|
int | 64 bit integer |
float | 64 bit float |
string | string |
byte | 8 bit byte |
bool | boolean true/false |
Type | Description |
---|---|
class | lightweight OOP-style class |
enum | enum of type byte |
array | collection of variables |
list | associative array/hash table |
matrix | mathematical matrix |
Variables are created by using the var
statement followed by the variable type and name. Ex:
var int MyVariable
or you can initialize it at the same time like this: var MyVariable = 45
and the variable will be initialized by the value type
In Coyote, you must assign a type to a variable, which stays the same until it's re-declared. Ex:
var lastName string
lastName = "Jones"
// This is fine
var firstName = "Fred"
var x int
x = 100
println(x)
var y = 200
// Error
firstName = 100
[line 9] Error at '100': Variable firstName is a Scalar of type string: cannot assign a Scalar of type integer
Declaring arrays
var x int[]
var y float[]
var z bool[]
...
You can also declare it as a sized array
var x = new int[3]
x[0] = 100
x[1] = 101
x[2] = 1
println(x[1])
// 101
If the array is initialized at the same time as it's declared, it takes the size of the initializer
var y = @[10,20,30]
println(y[1])
// 20
A declared but uninitialized array can be initialized (and sized) later
var z int[]
z = @[200,201,2]
println(z[1])
// 201
Dimensions in an array are delimited by commas. One comma indicates two dimension, two commas mean three dimensions, and so on. There is no practical limit to how many dimensions you can declare in an array.
var x = new int[3,3]
var y = 0
for i = 0 to 2 {
for j = 0 to 2 {
x[i,j] = y
y = y + 1
}
}
println(x[1,1])
// 4
As with regular arrays, the variable can be sized in advance
var m = new int[2,3,4]
var y = 0
for i = 0 to 1 {
for j = 0 to 2 {
for v = 0 to 3 {
m[i,j,v] = y
y = y + 1
}
}
}
println(m[1,1,1])
// 17
To declare a multi-dimensional array and initialize it at the same time, you can add [int,int]
at the beginning of the declaration of the array elements:
var x = @[[3,3]0,1,2,3,4,5,6,7,8]
x[2,2] = 4
x[1,1] = 1
x[0,0] = 100
println(x[2,2])
Lists contain elements of different types like − numbers, strings, arrays and even another list inside it. A list can also contain a matrix or a function as its elements. List is created as follows:
var l = @{"One":1, "Two":2, "Three":3}
var veggies = list[string,float]
veggies$Tomatoes = 2.00
veggies$Celery = 3.50
veggies$Spinach = 2.75
println(veggies$Celery)
// 3.50000
List of arrays:
var x = @{
"Q1":@["Jan","Feb","Mar"],
"Q2":@["Apr","May","June"]
}
println(x$Q2[1])
// May
Array of Lists:
var food = @[
@{"Carrots":1.75,"Celery":3.50, "Onions":0.75},
@{"Beef":4.55,"Pork":5.75,"Chicken":2.80}
]
println(food[0]$Celery)
println(food[1]$Pork)
// 3.5000
// 5.7500
Enums elements represent int
values
var size = enum {
XTRALARGE,
LARGE,
MEDIUM,
SMALL
}
var x = size.LARGE
if x == size.LARGE {
println("It's large")
}
// It's large
Functions don't have names, they return a function type which is stored in a variable. If you pass parameters, you must use a name:type expression followed by a return type if there is one. If there is a declared return type, it must be explicitely returned with the return
keyword
var f = func(x:int, y:int) int {
return x * y
}
println(f(4,5))
// 20
Functions can be passed as parameters to other functions:
var f = func(fn:func) {
fn()
}
var SayHello = func() {
println("Hi")
}
var SayBye = func() {
println("Bye")
}
f(SayHello)
f(SayBye)
// Hi
// Bye
A closure in Coyote is a function that is able to bind objects the closure used in the environment is was created in. These functions maintain access to the scope in which they were defined, allowing for powerful design patterns similar to concepts of functional programing
Suppose you want a function that adds 2 to its argument. You would likely write something like this:
var add_2 = func(y:int) int {
return 2 + y
}
add_2(5)
// 7
Now suppose you need another function that instead adds 7 to its argument. The natural thing to do would be to write another function, just like add_2, where the 2 is replaced with a 7. But this would be grossly inefficient: if in the future you discover that you made a mistake and you in fact need to multiply the values instead of add them, you would be forced to change the code in two places. In this trivial example, that may not be much trouble, but for more complicated projects, duplicating code is a recipe for disaster.
A better idea would be to write a function that takes one argument, x, that returns another function which adds its argument, y, to x. In other words, something like this:
var add_x = func(x:int) func {
return func(y:int) int {
return x+y
}
}
var f = add_x(7)
println(f(5))
// 12
var g = add_x(10)
println(g(5))
// 15
Control flow is the order in which we code and have our statements evaluated. That can be done by setting things to happen only if a condition or a set of conditions are met. Alternatively, we can also set an action to be computed for a particular number of times.
var f = func(x:int) {
if x > 5 {
println("> 5")
} else {
println("Not > 5")
}
}
f(4)
f(6)
// Not > 5
// > 5
A switch statement is a shorter way to write a sequence of if/else statements. It executes the first case whose value matches the condition expression.
var f = func(x:int) string {
var msg string
switch x {
when 0: {msg = "Zero"}
when 1: {msg = "One"}
when 2: {msg = "Two"}
default: {msg = "Big number"}
}
return msg
}
println(f(2))
println(f(6))
// "Two"
// "Big number"
Case is a variation of the 'switch' statement which doesn't use an initial condition expression. Instead, it tests individual expressions in each branch.
var f = func(x:int) string {
var msg string
case {
when x == 0: {msg = "Zero"}
when x == 1: {msg = "One"}
when x == 2: {msg = "Two"}
default: {msg = "Big number"}
}
return msg
}
println(f(2))
println(f(6))
// "Two"
// "Big number"
While loops are used to loop until a specific condition is met
var x = 1
while x <= 3 {
println(x)
x = x + 1
}
// 1
// 2
// 3
The coyote for loop syntax is as follows:
for <variable name> = <integer expression> to <integer expression> [step <integer expression>]
If the variable name isn't already in scope, this statement will create it inside the scope of the loop. The integer expression used
in the clauses can be either constants or an expression that evaluates to an integer. If you omit the step clause, it'll default to 1
Standard for loop
for i = 1 to 3 {
println(i)
}
// 1
// 2
// 3
For loop with a step
for i = 1 to 10 step 2 {
println(i)
}
// 1
// 3
// 5
// 7
// 9
We can use any expression that returns an integer to set the to ranges
var r = func() int {
return 4
}
for i = 1 to r() {
println(i)
}
// 1
// 2
// 3
// 4
Scan is a way to iterate over object collections such as arrays and lists. The first element in the declaration is the name of the collection variable while the target variable is a scalar containing the value of the current iteration
var x = new int[5]
for i = 0 to 4 {
x[i] = i
}
scan x to y {
println(y)
}
// 0
// 1
// 2
// 3
// 4
You can interrupt a loop prematurely using the break command
var x = 1
while x <= 10 {
println(x)
x = x + 1
if x == 4 {
println("Exit!")
break
}
}
// 1
// 2
// 3
// Exit!
Use continue to return to the original loop without running commands after this statement
var x = 1
while x < 5 {
x = x + 1
if x < 3 {
continue
}
println(x)
}
// 3
// 4
// 5
A class is a user-defined blueprint or prototype from which objects are created. Classes provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state. Class instances can also have methods (defined by its class) for modifying its state.
var myClass = class {
int a
int b
sum(x:int y:int) int {
return x+y+this.a+this.b
}
}
var x = new myClass
x.a = 6
x.b = 4
println(x.sum(3,4))
// 17
var y = new myClass
y.a = 10
y.b = 20
println(y.sum(3,4))
// 37
Coyote bundles together tools for organizing dataframes, querying data, and developing analytical applications. The language embeds the SQLite engine which offers all the functionality of the database engine while supporting Coyote's syntactic enhancements to the SQL language. You can store native Coyote data objects in columns and to use application variables inside SQL statements directly.
The key, distinctive feature of Coyote's SQL engine is that it's fully integrated into the language. That means that there is no connection to establish locally, and no translation of column data types into naive data types since they're one and the same. Simply write your SQL in the application as you would any other command or expression. All SQL statements need to end with a ;
You can use standard SQL syntax to create tables. All data types which ara available in Coyote can be used as data types for the columns
create table Person
(
first_name string,
last_name string,
age int
);
You can also create tables as a result of a query. This creates and populates a table based on the results of a SELECT statement. The table has the same number of columns as the rows returned by the query. The name and type of each column is the same as the name of the corresponding column in the result set of the SELECT statement:
create table Person2 as select * from Person where age < 50;
The DEFAULT clause assignes a default value to a column when no value is provided by the user when doing an INSERT. If there is no explicit DEFAULT clause attached to a column definition, then the default value of the column is NULL
create table Companies
(
CompanyName string NOT NULL
Address1 string
Address2 string
NumberOfOfficers int DEFAULT (0)
);
insert into Person (first_name, last_name, age) values ("John","Smith",28)
insert into Person (first_name, last_name, age) values ("Mary","Jones",42)
insert into Person (first_name, last_name, age) values ("George","Carlin",66)
select first_name, last_name, age from Person
Any variable can be used in the place of any element of a SQL query by using a $
in front of the variable: