Getting Started with Quail v2.0 - Quail-Language/quail GitHub Wiki
To begin Quail development, you must install Quail interpreter (Download).
Also, a good text editor with Quail syntax support is recommended (e.g. Sublime Text). You can download custom syntax highlighting files from Quail download page.
If you installed Quail via installer (or did everything correctly during manual install) you can go to Command line/Terminal/Shell and type:
quail info
It should give you information about currently installed Quail version. If it does, then everything's okay and we can move forward.
After doing that, you can write a simple "Hello, world" to test your development environment. Here's a snippet:
print("Hello, world!")Write this, save it in a file (let's call it test.q). Now you can run it:
quail run path_to_your_quail_code_file.q
For example, if I open terminal in the same folder where test.q is located, I can do:
quail run test.q
Run it. As you can see, print(something) puts that something into console.
We will use print(something) construction a lot, so try to remember how that's written.
Computers were designed to calculate something, so let's make our computer do what it's supposed to do.
To calculate something on your own, you should have some numbers and math symbols. E.g. 2 + 4 * 5. This is what's known as an expression.
In programming we also use expressions and, in fact, they are pretty similar to math expressions. We can write expression written above and try to make computer calculate it for us.
Practice 1.1:
Write a program that calculates
2 + 4 * 5and puts the answer into the console
If you didn't manage to write a functioning program, don't give up - you're just learning. Anyway, there's snippet for 1.1:
print(2 + 4 * 5)So, what do we see here? 3 numbers and 2 operators, right? As you can see, we can introduce a number just by writing it down. And notice this thing - 4 * 5 is calculated first, as we do it in maths. This is called operator precedence, when one operation is preferred over another.
Are you ready for another practice?
Practice 1.2:
Try to modify program from 1.1 and make it calculate
2 + 4first.Hint: programming is a lot like maths
So, there the answer:
print( (2 + 4) * 5 )Again, just like in maths, we use () to denote preferred operation.
Also, notice that I put lots of whitespaces between (()) ? Actually, it does not make any difference, it just makes it easier to read. But this is not the way it should be written. In actual projects there's no big whitespaces between braces and the snippet looks like:
print((2 + 4) * 5)And it still acts the same! Remember - whitespaces do not make big difference.
Practice 1.3:
Write a program that calculates sum of 10 and 45, then multiplies it by 2
Challenge: ... and then divides it by 3
There are the answers:
print((10 + 45) * 2)print((10 + 45) * 2 / 3)So, we have another new operation - division and it is denoted by / slash. Actually, there are more than 4 basic arithmetic operations. I've listed them below:
| Operator | Operation | Example |
|---|---|---|
a + b |
Sum a and b
|
2 + 4 == 6 |
a - b |
Subtract b from a
|
3 - 5 == -2 |
a * b |
Multiply a and b
|
7 * 6 == 42 |
a / b |
Divide a by b
|
5 / 2 == 2.5 |
a // b |
Divide a by b and make result an integer (floor) |
5 // 2 == 2 |
a ^ b |
a in power of b
|
6 ^ 2 == 36 |
a % b |
Division remainder | 5 % 2 == 1 |
- a
|
Negative a
|
-(2 + 3) == -5 |
a ^ 0.5 |
Square root of a
|
25 ^ 0.5 == 5 |
Pretty simple, isn't it?
Another thing I want to cover in this chapter is negative and non-integer numbers. I think text descriptions here would be redundant, so I'll just write correct and incorrect ways of writing a number in Quail.
Correct:
print(2)
print(-4)
print(0.5)
print(-0.6)
print(3.1415)
print(4000000)Incorrect:
print(0,4)
print(.4)
print(-.3)
print(4_000_000)So now, when we have covered everything about numbers, let's proceed to the final practice.
Practice 1.4
Write a program that calculates sqrt of one thrid of the circumference (length of a circle's outline) of a circle with
$R = 4 + 5$ (radius)Tip: The formula is
$\sqrt {{2 \pi R} \over {3}}$ Do not delete this program, we'll return to it later
Answer to final practice:
print(((2 * 3.1415 * (4 + 5)) / 3) ^ 0.5)Our output from 1.4 is not very informative. It's only a number without any description. Let's now add a label to this output. And to do that we'll need strings.
String is basically text. Syntactically, it's denoted with double quotes " at the start and at the end. Example:
print("Oh, hello there!") But how we can add some description, like Answer is to our number input? You can use + operator to add string to another string. Let's try it!
print("Answer is " + ((2 * 3.1415 * (4 + 5)) / 3) ^ 0.5)And... This returns an error. Why? That's the reason: we are trying to add string to a number, not string to string. We can convert number to string using type conversion
Type to string conversion
Syntax:
string(value)Returns: A string containing text representation of
value
Try to fix our snippet by yourself!
Practice 2.1: Modify the snippet and make it work without errors
Snippet:
print("Answer is " + ((2 * 3.1415 * (4 + 5)) / 3) ^ 0.5)Do not delete this program, we'll return to it later
The answer is:
print("Answer is " + string(((2 * 3.1415 * (4 + 5)) / 3) ^ 0.5))We'll come to strings later, but for now this knowledge is enough.
Imagine that you should make a program that can calculate
At the moment we know only one function - print. It takes some value and puts into console. Now welcome its relative - input!
inputFunctionSyntax:
input()orinput(message)Action: Waits until user puts something in console and presses
Enter, then returns inputted value. Ifmessageis provided, then prints an input prompt Returns:string
Get ready for another task
Practice 3.1:
Modify answer from 2.1 to take input instead of hard-coded radius.
Do not forget:
$R$ was$4 + 5$
The answer is:
print("Answer is " + string(((2 * 3.1415 * input()) / 3) ^ 0.5))But, does it work? Let's test it. Unfortunately, no. And the reason here is similar to reason why snippet from Chapter II did not work. We are trying to multiply a number by a string. This is not possible, so Quail gives an error. And the solution is actually very similar to 2.1 solution. Just add a type conversion. But this time - from string to number.
Type to number conversion
Syntax:
num(value)Returns: Number representation of a
valueCan throw an error when
valueis not a valid number
So, here's another practice:
Practice 3.2:
Modify the snippet and make it work
print("Answer is " + string(((2 * 3.1415 * input()) / 3) ^ 0.5))
And the answer is:
print("Answer is " + string(((2 * 3.1415 * num(input())) / 3) ^ 0.5))The final practice:
Practice 3.3:
Modify the 3.2 answer and make it display an input prompt
"R="Do not delete this program, we'll return to it later
Answer to final practice:
print("Answer is " + string(((2 * 3.1415 * num(input("R="))) / 3) ^ 0.5))Our snippet gets bigger and more complicated overtime, but don't worry - we'll resolve this problem right now.
It will be great if we could not only calculate, but also store data. And, actually, we can! We can use variables.
Variable is some sort of a cell in memory. It has a name and a value assigned to it. We can assign any variable at any moment using this syntax: variable = value. We can retrieve data from variable just by writing its name. Referencing to unassigned variable returns null.
Variable name can contain only A-Z, a-z letters, underscores, and numbers (only after at least one letter)
Correct variable names: hello, d2, this_is_MY_variable
Incorrect variable names: @hello, 2d, this is MY variable
Example of variable usage:
a = 10 + 5
b = num(input())
print(a * b + 45)Practice 4.1
Refactor answer to 3.3. You should store input,
$\pi$ , whole circumference and stringified answer in four separate variables.Refactoring is the process of restructuring code, while not changing its original functionality. The goal of refactoring is to improve internal code by making many small changes without altering the code's external behavior
Do not delete this program, we'll return to it later
Answer to 4.1:
R = num(input("R="))
pi = 3.1415
C = 2 * pi * R
answer = string((C / 3) ^ 0.5)
print("Answer is " + answer)Now your program is looking way better and you can clearly tell what part of it does what.
One little thing I need to tell you about - the null. Null is literally nothing. It's not even null, if you'll try - it'll give an error.
Let me introduce new data type - boolean. Boolean is a very simple type, it can represent only either false or true value. In Quail, you can write false or true to create a boolean. There are also available opertions for booleans and type converter.
Type to boolean conversion
Syntax:
bool(value)Returns: New boolean value that matches the
valueIf
valueis string then acceptable values are"true"or"false"If
valueis number then any value apart from0will betrue,0will befalseCan throw an error if
valueis not a boolean.
Boolean operations:
| Operator | Operation | Example |
|---|---|---|
a and ba && b |
Logic 'and' |
true and false == falsetrue and true == truefalse and false == false
|
a or ba `| |
` b | Logic 'or' |
! a |
Logic inversion |
!true == false!false == true
|
Also or is a bit special. If a and b operands are booleans, then it acts as logic 'or', but in other case, table below is applied:
| a | b | a or b |
|---|---|---|
| Not null | Not null | Returns a
|
| Null | Not null | Returns b
|
| Not null | Null | Returns a
|
| Null | Null | Returns null
|
There are also available comparison operators:
| Operator | Operation | Example |
|---|---|---|
a > b |
a is greater than b? |
4 > 2 == true |
a < b |
a is less than b? |
4 < 2 == false |
a >= b |
a is greater than or equal to b? |
3 >= 3 == true |
a <= b |
a is less than or equal to b? |
5 <= 3 == false |
a == b |
a is equal to b? |
6 == 6 == true |
a != b |
a is not equal to b? |
5 != 6 == true |
Now when you know a lot more about logic and comparison, get ready for practice.
Practice 5.1:
Modify answer to 4.1 and make it ask not only radius, but also user's expectations about the value. Then, print message that says was user correct or not.
Tip: look how $R$ is inputted
Also make it calculate just the circumference.
Tip: delete the
answervariable, and convert to string only circumferenceDo not delete this program, we'll return to it later
And the answer is:
R = num(input("R="))
expects = num(input("Guess: "))
pi = 3.1415
C = 2 * pi * R
print("Answer is " + string(C))
print("Did expectations met? " + string(expects == answer))At the moment we can input negative radius and that is a problem. But now we can check whether the value is positive (x > 0). And now let me introduce the first construction - if.
ifconstructionSyntax:
if condition action_if_true else action_if_falseElse can be skipped:
if condition action_if_trueIf condition is
true, then codeaction_if_truewill be done. Else if there iselseconstruction, then codeaction_if_falsewill be done. If there is noelseand condition is nottrue, then nothing will happen,ifwill be just skipped.
Notice that
space. This can be achieved through pressingTABkey.
With this construction we can check something and make decisions. E.g.:
n = 432552
guess = num(input("Enter your guess"))
if n == guess
print("You've guessed the number!")
else
print("Sorry. You are incorrect.")This snippet provides us possibility to set some number and then make user guess it. If user is correct, then one message will be displayed, else other.
Practice 6.1
Now modify the program, so that it will display an error if user's
$R < 0$ , else it will display$2{\pi}R$ Also, remove guessing functionality
Do not delete this program, we'll return to it later
And here comes the answer:
R = num(input("R="))
pi = 3.1415
C = 2 * pi * R
if R < 0
print("R < 0!")
else
print("Answer is " + string(C))Let's say we want to put more than one line into if construction. How we can do that? One could think about this kind of approach:
R = num(input("R="))
if R < 0
print("R < 0!")
print("Try again")But when we run that program, Try again is printed every time, even if R >= 0. That is because of how language sees your code.
There is a basic concept, that's called statement. According to Wikipedia,
statement is a syntactic unit of an imperative programming language that expresses some action to be carried out. A program written in such a language is formed by a sequence of one or more statements. A statement may have internal components (e.g. expressions)
To put it simply, a statement can be equated to the line: one line - one statement.
The if construction looks for one statement after condition and then considers the if section complete. To pack multiple statements to one we can use blocks.
Block is a statement that contains an ordered collection of statements. When block is being executed, each statement in that block will be executed one-by-one from the first to the last. Blocks are usually denoted with curly braces, but in Quail there are other ways to denote blocks:
# The normal way
if condition {
print("This is statement 1")
print("This is statement 2")
print("This is statement 3")
}
# The Lua way
if condition then
print("This is statement 1")
print("This is statement 2")
print("This is statement 3")
end
# The Python way
if condition:
print("This is statement 1")
print("This is statement 2")
print("This is statement 3")
# Btw, you can wrap single or no statements whatsoever:
if condition {}
if condition {
print("This is the only statement")
}For simplicity, we will use curly-brace-denoted block throughout this whole tutorial.
Practice 7.1
- Modify your program to print "Try again!" after printing "R < 0!"
Do not delete this program, we'll return to it later
The answer is:
R = num(input("R="))
pi = 3.1415
C = 2 * pi * R
if R < 0 {
print("R < 0!")
print("R < 0!")
} else
print("Answer is " + string(C))Keep your circumference calculator and start a new program.
Now we want to calculate a factorial of some inputted number.
Factorial of $n$ is a product of all numbers from $1$ to $n$, including $n$. Often written as $n!$
Example: $4! = 1 \cdot 2 \cdot 3 \cdot 4$
How do we do that? Now loops will come in handy.
Loop is a statement that is executed many times, while some condition is met.
The simplest loop is while:
whileconstructionSyntax:
while condition statementExecutes
statementrepeatedly whilecondition == true
So, how we should calculate the factorial? We can start from n for inputted number, i for increasing number and r for result
The answer is:
n = num(input())
i = 1
r = 1
while i <= n {
r = r * i
i = i + 1
}
print(r)This will give us the factorial. But is does look quite ugly, doesn't it? We can fix this using through loop:
throughconstructionSyntax:
through start_number:end_number as iterator statementBehaviour:
Starts at
iterator = start_numberExecutesstatementand each time movesiteratorby$1$ . Continues whileiterator < end_number.Example:
through 1:11 as i { print(i) }Will output:
1 2 3 4 5 6 7 8 9 10Note that 11 is not included. To include it, write
:+instead of::through 1:+11 as i { print(i) }Will output:
1 2 3 4 5 6 7 8 9 10 11
Ready for another practice?
Practice 8.1
Rewrite previous factorial program using
throughinstead ofwhileTip: do not forget to include $n$ in loop
Do not delete this program, we'll return to it later
The answer is:
n = num(input())
r = 1
through 1:+n as i
r = r * i
print(r)Combine your circumference calculator and factorial calculator into one program
Tip: first ask user for the action (factorial or circumference) then with
ifand==choose the right code.Tip 2: you can combine
elseandifto make conditional elseDo not delete this program, we'll return to it later
The answer is:
action = input("Circumference (C) or Factorial (F)? ")
if action == "C" {
R = num(input("R="))
pi = 3.1415
C = 2 * pi * R
if R < 0 {
print("R < 0!")
print("R < 0!")
} else
print("Answer is " + string(C))
} else if action == "F" {
n = num(input())
r = 1
through 1:+n as i
r = r * i
print(r)
}Despite all your tries, code again looks quite messy. Let's see another instrument for making code clearer - a function.
Function is a packaged sequence of program instructions that performs a specific task. This function can then be used in programs wherever that particular task should be performed.
functionconstructionSyntax:
function function_name() statementor
function function_name(function_argument1, function_arg2, ...) statement
We can call a function using its name and a pair of round braces:
function_name()We can also return a value from a function:
function add(a, b) {
return a + b
}
c = add(5, 9)
print(c)Let's now split our code using functions. Yes, another practice!
Practice 9.1
Split your code into different functions - one for circumference, other for factorial
Do not delete this program, we'll return to it later
The answer is:
function circumference() {
R = num(input("R="))
pi = 3.1415
C = 2 * pi * R
if R < 0 {
print("R < 0!")
print("R < 0!")
} else
print("Answer is " + string(C))
}
function factorial() {
n = num(input())
r = 1
through 1:+n as i
r = r * i
print(r)
}
action = input("Circumference (C) or Factorial (F)? ")
if action == "C"
circumference()
else if action == "F"
factorial()The code is much more understandable now!
Let's now say we want to calculate average of some collection of numbers. We can do it easily with 2 numbers:
a = num(input())
b = num(input())
print((a + b) / 2)We can easily do it with 3 and 4 numbers, but when it comes to big sequences, e.g. 100 or 200 it becomes pretty difficult. And more than that, what if we want to calculate average of sequence of unknown size? List will come in handy.
List is an ordered collection of different elements, with varying size. You can add, remove, insert, access elements and do much more with them.
Defining an empty list
l = []Defining a list:
l = [1, 3, 5, 7]Adding element to list
l.add(9)=>[1, 3, 5, 7, 9]Removing element from list
l.remove(1)=>[3, 5, 7, 9]Accessing element in list
l[0]=>3. Note that in programming we count from zero, sol[0] - first elementSize of list
l.size()=>4
So, how do we input a varying-long sequence of numbers?
strings = input().split(" ")
numbers = []
through 0:strings.size() as i {
numbers.add(num(strings[i]))
}Let's explain every line:
-
We input a string and split it using space as separator
string.split(delimiter)
Splits string into parts using provided delimiter. Returns list of strings
-
We create an empty list, where we will put our numbers
-
Loop through 0 to size of string list, store in
i -
For each index, get string with corresponding index in
stringslist, convert it to a number and add it intonumberslist
As a result, we get list numbers that contains all numbers, that user has inputted in the following format: 1 3 5 7 9
Practice 10.1
Make a new program that calculates average of all numbers inputted by user. Use code provided above.
Tip: $avg(x_1, x_2,...,x_n) = {1 \over n } \cdot (x_1 + x_2 + ... + x_n)$
Do not delete this program, we'll return to it later
Answer to 10.1:
strings = input().split(" ")
numbers = []
through 0:strings.size() as i {
numbers.add(num(strings[i]))
}
sum = 0
through 0:numbers.size() as i {
sum = sum + numbers[i]
}
print(sum / numbers.size())List operations:
| Left | Operator | Right | Operation | Example |
|---|---|---|---|---|
| List | + |
List | Concatenate (add) two lists | [1, 2] + [3, 4] == [1, 2, 3, 4] |
| List | * |
Number | Multiply list | [1, 2] * 2 == [1, 2, 1, 2] |
| List | / |
Number | Divide list |
[1, 2, 3, 4] / 2 = [[1, 2], [3, 4]][1, 2, 3, 4, 5] / 3 = [[1, 2], [3, 4], [5]]
|
| List | // |
Number | Divide list into equal parts and throw off remainder |
[1, 2, 3, 4] // 2 = [[1, 2], [3, 4]][1, 2, 3, 4, 5] // 3 = [[1], [2], [3]]
|
| List | >> |
Number | Barrel-shift all values to right by certain number | [1, 2, 3] >> 1 == [3, 1, 2] |
| List | << |
Number | Barrel-shift all values to left by certain number | [1, 2, 3] << 1 == [2, 3, 1] |
| List |
<, >, >=, <=
|
List | Compare two lists by size | [1, 2] > [1] == true |
| List |
==, !=
|
List | Compare each element in list using ==
|
[1, 2] == [1, 2] -> trueList methods: |
List methods: See Quail Core Docs on List
Now, when we introduced lists, time has come for range, sublist and for loop.
Let's start with range: Range is a sequence of numbers from :, included when denoted with :+), where each one differs from its neighbours by n:m, n:m:k, n:+m, n:+m:k.
Examples:
-
0:5=[0, 1, 2, 3, 4] -
0:+5=[0, 1, 2, 3, 4, 5] -
3:0=[3, 2, 1] -
0:10:2=[0, 2, 4, 6, 8]
Range immediatly creates a list, when it is being executed.
Moving forward to sublists: Sublist is a list that has been created from a certain part of other list, starting from index :, included when denoted with :+) with a certain step
Examples:
Assume l = ["a", "b", "c", "d", "e", "f"]
-
l[0:2]=["a", "b"] -
l[0:+2]=["a", "b", "c"] -
l[0:+4:2]=["a", "c", "e"]
There are a few tricks with sublists:
-
l[:2]=["a", "b"]l[:n]- from start to$n$ -
l[4:]=["e", "f"]l[n:]- from$n$ to end -
l[::-1]=["f", "e", "d", "c", "b", "a"]Basically, it says: from start to end, every element with step -1 (backwards), so it reverses the list
A little tip: you can use this syntax on strings too
Let's move to for list:
forconstructionSyntax:
for iterator in collection statementBehaviour:
Start at first element in
collection.iteratoris the current element. Walks through all elements in collection, keeping original order.Example:
for n in 0:5 { print(n) }Will output:
0 1 2 3 4Strings are also supported
for character in "abc" { print(character) }Will output:
a b c
Practice 11.1
Refactor 10.1 using for loop
Do not delete this program, we'll return to it later
Answer to 11.1:
strings = input().split(" ")
numbers = []
for s in strings {
numbers.add(num(s))
}
sum = 0
for n in numbers {
sum = sum + n
}
print(sum / numbers.size())Let's take a break from maths, and do something else. So, what if your calculator needs to be able to display error messages in different languages? The easiest way to do it is a dictionary.
Dictionary is an unordered key-value structure. Each unique key corresponds to certain value. Syntax is: {key=value, key=value, key=value}. You can put items into dictionary, access them, etc. Examples:
Assume d = {"one" = 1, "two" = 2, "three" = 3}
-
d["one"]=1 -
d["ten"]=null(that key is not present, so it's null) -
d["ten"] = 10 -
d["ten"]=10(now we set "ten" -> 10) -
d.keys()=["one", "two", "three", "ten"](not necessarily in that order) -
d.values()=[1, 2, 3, 10](not necessarily in that order) -
d.pairs()=[["one", 1], ["two", 2], ["three", 3], ["ten", 10]](not necessarily in that order) -
d.size()=4(number of key=value pairs)
Notice, that dictionaries, unlike lists and strings, do not have strict order, therefore there are no indexes. Dictionaries can only be iterated by keys:
for key in d.keys() {
print(d[key])
}Also you should note, that all keys in Quail must be strings.
Let's return to our for loop. It has a secret ability: it can iterate more that one iterator at once. Basically it works like that: let's say we have a loop for a, b in l { ... }. The loop will one-by-one go through elements of l and try to unpack them. If element is a list and its size is equal to iterator count, then each sub-element of that element will be mapped to its iterator. In any other case, there will be an error. Example:
d = {"a" = 1, "b" = 2, "c" = 3}
for key, value in d.pairs() {
print(key)
print(value)
print("---")
}Will output: (not necessarily in that order)
a
1
---
b
2
---
c
3
---
Let's return to our translator problem, here is the solution:
lang = {
"en" = {
"picked_language" = "Language picked",
"farewell" = "Bye!"
},
"de" = {
"picked_language" = "Sprache ausgewählt",
"farewell" = "Tschuss!"
}
}
userLang = input("Choose your language: ")
print(lang[userLang]["picked_language"])
print(lang[userLang]["farewell"])We are storing dictionaries of different translations under their language keys, then letting user select language, then selecting suitable message dictionary and accessing messages from it.
Practice 12.1
Modify the input line in program above to display all possible languages
Tip: use keys() method
Answer to 12.1:
lang = {
"en" = {
"picked_language" = "Language picked",
"farewell" = "Bye!"
},
"de" = {
"picked_language" = "Sprache ausgewählt",
"farewell" = "Tschuss!"
}
}
print("Available languages:")
for language in lang.keys() {
print(language)
}
userLang = input("Choose your language: ")
print(lang[userLang]["picked_language"])
print(lang[userLang]["farewell"])Dict operators:
| Left | Operator | Right | Operation | Example |
|---|---|---|---|---|
| Dict |
==, !=
|
Dict | Compare each key-valuie pair in dict using ==
|
{"a" = 1, "b" = 2} == {"a" = 1, "b" = 2} -> true{"a" = 1, "b" = 2} == {"a" = 1, "c" = 2} -> false
|
Dict methods: See Quail Core Docs on Dict
It turns out that this chapter is quite confusing and chaotic, please try to understand it)
Let's return to our code from 11.1. We can simplify it further using list generators. List generator is an expression that is repeatedly evaluated in some loop and in the end returns a list. Basically, it's a for or through loop with list.add in it. Let's take a closer look at some examples:
-
[i through 0:5 as i]->[0, 1, 2, 3, 4]Each iteration, elementiis evaluated (which corresponds to currently iterating number) and added into list. -
[i through 0:5 as i if i % 2 == 0]->[0, 2, 4]Same as above, but it contains a condition: if condition is true, element is added, else - skipped. -
[character for character in "abc"]->["a", "b", "c"]Same as through, but this time - it uses for
Now we can transform this:
strings = input().split(" ")
numbers = []
for s in strings {
numbers.add(num(s))
}To this:
numbers = [num(s) for s in input().split(" ")]Dictionary generators also are available:
Assume l = "abc"
-
{l[i] = i for i in 0:l.size()}->{"a" = 0, "b" = 1, "c" = 2}
Nonetheless, we can simplify this number convertor further, using map function:
map(func action, list collection)functionBehaviour: applies function
actionfor each element incollection.Example:
function f(x) return x^2map(f, [1, 2, 3])Returns[f(1), f(2), f(3)]=[1, 4, 9]
So, we can just write this:
numbers = map((x) -> return num(x), input().split(" "))But, what's that (x) -> return num(x) thing out there? This is called anonymous function. It's like a regular function, but it doesn't have a name, so it's not polluting your namespace. Anonymous functions (also called lambdas) are useful when you need to pass some action as a parameter. The syntax for lambda is: (arg1, arg2, ...) -> statement or function (arg1, arg2, ...) statement.
You can place lambdas in variables to achieve same thing you could do with regular function definitions. E.g. these two snippets are doing the same thing:
function sqrt(x) {
if x < 0 return -1
return x^0.5
}sqrt = (x) -> {
if x < 0 return -1
return x^0.5
}All these things are essentials for the so-called functional programming, which is very effective when you are dealing with data handling. Let's learn another function for this paradigm - the sum function:
sum(list collection)functionReturns sum of all numbers in given collection
Example:
sum([1, 2, 3])=6
Let's now modify our average value calculator using all tools we've learned:
numbers = map((x) -> return num(x), input().split(" "))
print(sum(numbers) / numbers.size())As you can see, it's much more clear and smaller than than our imperative implementation:
strings = input().split(" ")
numbers = []
for s in strings {
numbers.add(num(s))
}
sum = 0
for n in numbers {
sum = sum + n
}
print(sum / numbers.size())You can clearly see the advantage of functional programming when handling data.
P.S.: If you've understood this chapter - congrats, it's one of the most confusing and difficult chapters here (in my opinion)
Uh oh, that's a heavy task.
Combine factorial calculator, circumference calculator and average calculator
Extend average calculator - it should also display min and max number
Implement different parts of calculator with functions
Tip: to calculate min and max, you can use
minandmaxfunctions. They work likesum. More info about this in Quail Core Docs on min, maxDo not delete this program, we'll return to it later
The answer is:
function circumference() {
R = num(input("R="))
pi = 3.1415
C = 2 * pi * R
if R < 0
print("R < 0!")
else
print("C=" + string(C))
}
function factorial() {
n = num(input())
r = 1
through 1:+n as i
r = r * i
print(string(n) + "!=" + string(r))
}
function averageMinMax() {
numbers = map((x) -> return num(x), input().split(" "))
print("Average" + string(sum(numbers) / numbers.size()))
}
print("Available actions:")
print("Circumference C")
print("Factorial F")
print("Avg,min,max A")
action = input("Action? ")
if action == "C"
circumference()
else if action == "F"
factorial()
else if action == "A"
averageMinMax()Okay, let's now move to some theory. We are going to talk about object-oriented programming. Let's define some concepts:
Object is a packaged collection of variables (fields) and functions (methods). Usually object is a derivative of some class.
Class is a template for creating objects. It defines fields and methods for its objects.
Constructor is a method that created an object from some class (this process is also called instantination or in Quail - derivation).
Let's now get some examples from our world. You can think of an object as a building. It has various parameters, methods to open/close entrance, etc. In this case you can think of a class as it's a blueprint for that building - it defines its parameters and different functions. Constructor is basically the construction team. It creates a new building based on some blueprint you give to them. I hope everything is more clear now and we can proceed on looking at syntax.
Defining a class:
class Building {}Adding fields:
class Building {
width = 0
length = 0
height = 0
}Adding methods:
class Building {
width = 0
length = 0
height = 0
function outerArea(this) {
return this.width * this.length
}
}Adding constructor:
class Building {
width = 0
length = 0
height = 0
constructor (this, width, length, height) {
this.width = width
this.length = length
this.height = height
}
function outerArea(this) {
return this.width * this.length
}
}Instantinating: (create new building with parameters 3, 4, 5 - passed to constructor)
building = Building(3, 4, 5)Accessing fields:
print(building.width)Calling methods:
print(building.outerArea())We need to stop here a little. Note how outerArea, constructor have this wierd first argument called this and note how we pass 1 argument less than needed? (3 args to constructor instead of 4, 0 args to outerArea instead of 1). This magic argument allows methods to access the object that they belong to. When you call some method on an object, Quail will automatically understand that this is an object and it will automatically insert the object as the first argument, moving all arguments to the right. So building.outerArea() will actually be building.outerArea(building). This mechanism allows us to access fields of an object inside its method or call other methods. Please note, that if you save method of an object somewhere else and call it as a function, Quail won't prepend object reference to arguments, so:
f = building.outerArea # Save building.outerArea method into f
f() # Won't work
f(building) # This is what you need to doPractice 14.1
Create a
Rectclass that resembles a rectangle. It should have fields for width, height, and a method for calculating its area.Then, make user be able to input width and height and see the area of inputted rectangle.
Answer to 14.1:
class Rect {
w = 0
h = 0
constructor (this, w, h) {
this.w = w
this.h = h
}
function area(this) {
return this.w * this.h
}
}
w = num(input("w="))
h = num(input("h="))
rect = Rect(w, h)
print(rect.area())Classes can inherit from one another. You can thing of it as creating a new blueprint based on other blueprint. Methods and fields from parent class will remain unless overridden. Inheritance looks like this:
class Letter {
capital = ""
small = ""
type = ""
constructor (this, type, capital, small) {
this.type = type
this.capital = capital
this.small = small
}
}
class Vowel like Letter {
type = "vowel"
constructor (this, capital, small) {
Letter._constructor(this, "vowel", capital, small)
}
}Here, class Vowel inherits from class Letter, gaining (implicitly) all Letter's fields and overriding the constructor. Note how we call a function with underscore from class? Yes, we can access all methods from class, and even call them, but you will need to manually pass this. E.g. Rect.area(rect) == rect.area(). Constructor is saved as a function with name _constructor. So we are basically calling defined in Letter constructor, but we are substituting type with our predefined "vowel" value.
Another useful thing - instanceof check. You can check whether or not an object is an instance of some class:
Assume: a = Vowel("A", "a"), b = Letter("consonant", "B", "b")
-
a instanceof Vowel->true-ais an instance of classVowel -
b instanceof Letter->true-bis an instance of classLetter -
a instanceof Letter->true-ais an instance of classVowelwhich is a child of classLetter -
b instanceof Vowel->false-bis not an instance of classVowelandb's class is not a child ofVowelclass
Methods and fields can be static. This means that they are not bound to some specific object, but they are common for the whole class. You can declare static fields and methods by using static modifier:
class AClass {
static someStaticField = 23412
static function f() {
...
}
}Static functions do not receive this argument, because they do not belong to some object. You can access statics like this:
print(AClass.someStaticField)
print(AClass.f())You can even access statics using an object:
anObject = AClass()
print(anObject.someStaticField)
print(anObject.f())The rest of their behaviour is similar to usual fields and methods
So here is another concept - you can add functionality to your class even after it is defined. Consider Rect class from Chapter XIV:
class Rect {
w = 0
h = 0
constructor (this, w, h) {
this.w = w
this.h = h
}
function area(this) {
return this.w * this.h
}
}What if you suddenly want to add a perimeter() function to it? You can do it just by setting a field with target name to an anonymous function with target functionality:
Rect.perimeter = (this) -> { return (this.w + this.h) * 2 }And there you have it. Of course, it's better to setup all functionality at once in class. And moreover I should say that you shouldn't use this construction unless absolutely necessary. But when can it be absolutely necessary? We'll talk about it in a little while. For now, let's learn about other concept:
Everything in Quail is an object
This means that everything in Quail has its own class? Yes, it does. We have Number, String, Bool, List, Dict and Object classes. Moreover, we have a Null class which represents the null value. You can do some instanceof experiments to see which value belongs to which class. But let's think about this for a moment: if everything in Quail is an object and thus has its class and we can dynamically add functionality to classes even after definition, doesn't this mean that we can add something to default Quail classes? And yes, it does. And this is the absolutely necessary case: you absolutely need to add something to a class, which declaration is not accessible. Let's see how we can do that:
Number.hello = "Hello!"
a = 3
print(a.hello)And the magic happens! Furthermore, we can access a field just out of number:
Number.hello = "Hello!"
print(3.hello)Actually, just out of any value:
Bool.promise = "I will call you later!"
print(false.promise)We can do that even with Null:
Null.a = "A"
print(null.a)Isn't it cool? We can even do some methods:
Number.add = (this, other) -> {
return this + other
}
print(17.add(4))Bool.inverse = (this) -> {
return !this
}
print(false.inverse())And the last concept I want to talk about is underscore methods. An underscore method is an utility method which helps Quail know what to do. For example: if I was to execute an addition on two objects, if addition was supported on these types (eg. Number + Number or String + String) it will be executed normally. But if that is not the case (eg. String ^ List) Quail will search for a specific method, in this case for _add method. If one will be found, operation will be delegated to it. If not, an UnsupportedOperationException will be raised (we'll talk about exceptions in next chapter). This way we can create custom operations. E.g. we can reverse a string using -:
String._neg = (this) -> {
return this[::-1]
}
print(-"Hello")The full table of operator and their corresponding methods is in the Quail Specification Chapter 6.
Imagine that something in your program has gone wrong. It can be anything - computer is out of memory, invalid data inputted, website in unaccessible, etc. How do you report such events? Maybe you can just print the error message. But what if you want to immediatly stop execution of current function, when an error occurrs? Exceptions will come in handy.
An exception is an object, which carries data about the error, but most importantly it can be thrown. When you throw an exception (sometimes this process can be called raising an exception) it blasts through the code and stops the execution. An exception can be catched. If so, it will stop and execution will continue. Let's see an example:
print("Here it comes...")
throw Exception("Here I am!")
print("This will never be executed")First line will be executed then on the second line exception will be created and thrown, thus stopping the flow and 3rd line will never be reached.
We can catch it:
try {
print("Here it comes...")
throw Exception("Here I am!")
print("This will never be executed")
} catch {
print("Caught!")
}If come exception will occurr in try-block, it will be catched and passed into catch-block. We can receive it there:
try {
print("Here it comes...")
throw Exception("Here I am!")
print("This will never be executed")
} catch as e {
print("Caught! Message: " + e.message)
}We can filter exceptions. Let's create our own exception first:
class MyException like Exception {
constructor (this) {
this.message = "I'm a custom exception"
}
}
try {
print("Here it comes...")
throw MyException()
print("This will never be executed")
} catch MyException as e {
print("Caught! Message: " + e.message)
}
try {
print("Here it comes...")
throw Exception("Here I am!")
print("This will never be executed")
} catch MyException as e {
print("Caught! Message: " + e.message)
}First try-block will be executed: print done, exception thrown. Then catch-block will be checked - it seeks for MyException - thrown exception is an instance of MyException, thus it's caught. In the second try-block we throw other exception, thus catch-block will not be executed and exception will not be caught.
Also, keep in mind that exceptions will blast through functions:
function haltAndCatchFire() {
throw Exception("AAAAH!")
}
function callHalt() {
haltAndCatchFire()
print("This will never be executed")
}
print("Here it comes...")
callHalt()
print("This will never be executed")Here: print done, callHalt being called, haltAndCatchFire being called, exception thrown, haltAndCatchFire interrupted and stopped and its caller - callHalt also interrupted and stopped, exception reaches main section and stops the code.
Practice 18.1
Modify our calculator (from Interchapter Practice 2) so it throws an exception when
$R < 0$ Do not delete this program, we'll return to it later.
Answer to 18.1 (only modifications are included):
function circumference() {
R = num(input("R="))
pi = 3.1415
C = 2 * pi * R
if R < 0
throw Exception("R < 0")
else
print("C=" + string(C))
}
...Another simple, yet useful concept: variable modifiers. Imagine a situation where you for example only want numbers passed into your function? How would you control that? Of course you can do:
function squared(n) {
if not (n instanceof Number)
throw Exception("Only numbers accepted!")
return n^2
}But that is bulky. Luckily Quail has a solution for you - variable modifier. It can be put before variable names (not before fields!) when you declare them or before argument names in functions. In both cases modifiers apply some restrictions to them. If we use modifiers in previous code, we get:
function squared(num n) {
return n^2
}This little num will prevent any other value apart from numbers from getting into this function. If someone will try - they will get an exception. Here is a list of all type modifiers:
| Modifier | Meaning |
|---|---|
num |
Only numbers (like Number) |
string |
Only strings (like String) |
bool |
Only booleans (like Bool) |
void |
Only nulls (like Null)Btw, who on earth will ever need it? |
list |
Only lists (like List) |
dict |
Only dictionaries (like Dict) |
func |
Only dictionaries (like Dict) |
notnullrequired
|
Only non-null values (opposite to void) |
object |
Any object (Does not really do anything, because everything in Quail is an object) |
There are also other modifiers:
| Modifier | Meaning |
|---|---|
local |
Forces write to a local variable (More info about scopes in Quail Specification Chapter 7) |
final |
Allows only one write to the variable after which it's locked |
static |
Makes fields and methods static (You already know this one) |
You can stack multiple modifiers:
local final string a = "Hello"Or if you want for example only numbers and bools, you can create separate cases:
function negate(num | bool x) {
if x instanceof Number
return -x
else if x instanceof Bool
return not x
}This way you can handle different data. But there is another, better way which is called method overloading. Essentialy, it means that if you declare a couple of functions with same names, but different argument modifiers then when you'll call it, the function will automatically resolve which code to call based on what types of arguments you pass:
function negate(num x) {
return -x
}
function negate(bool x) {
return not x
}When you pass a number, the first function will be called and hen you pass a bool, the second. If you pass something different you'll get an exception. You can also declare the so-called fallback:
function negate(num x) {
return -x
}
function negate(bool x) {
return not x
}
function negate(x) {
return null
}In this case no exception will be thrown when you pass something apart from boolean and number, because there is a case which does not restrict types. These all cases are called alternative calls.
Annotations and decorators are advanced concepts which you won't use (at least at first) really often. So we'll go a bit faster here. Basically they modify language constructions (more specifically functions and classes) each in their own way.
Decorator is a script-defined component. Decorator is a function which recieves class or function and output their modified version. Here is an example:
function traceExecution(f) {
return () -> {
print("Start execution")
f()
print("End execution")
}
}
@traceExecution
function sayHello() {
print("Hello!")
}
sayHello()Will output:
Start execution
Hello!
End execution
Basically, when sayHello function is created, it's original is passed into traceExecution which creates another function which does some its own business as well as calls the original function and this new function is then automatically places into sayHello.
Annotation is a language-defined component. This means you cannot create them. They also do some modifications. They are described in Quail Specifications. At the moment of writing of this chapter, only one annotation is available: @Deprecated. This can tell other developers that this functionality is deprecated, not good to use and maybe subjected to removal.
Not all functionality is accessible by default. Some is packaged into libraries. Library is a collection of externally defined functions, values and classes which you can use in your program. For example, if you want to generate some random numbers, you should use lang/random library. Let's see an example:
use "lang/random" = random
print(random.random())Here, we use the library and place it into random variable. From here we can access its contents. The random function randomizes a real number between 0 and 1.
In the previous chapter we've learned how to use libraries. Now we will learn how to create them.
Let's return back to our calculator and take the algorithm we've used to calculate the circumference and put it into a function:
function circumference(R) {
return 2*3.14*R
}We can create a library with this function like that:
- We create a new file (let's name it
mathlib.q) - We will then write this function in this file
- Then, at the very bottom of the file we will return a dictionary that contains our function
So we will get this:
function circumference(R) {
return 2*3.14*R
}
return {"circumference": circumference}Now, in the main file we will write this:
use "mathlib.q" = math
print(math.circumference(4))And that will work! So basically what use command does is that it grabs a file that is specified in quotes, runs it, gets what it returns and whatever is returned is placed into the variable that is specified to the right of equals sign. What's more important is that the library's runtime retains and that means that library function can access other functions in the same library.
Another important thing to understand is that if a library was requested earlier, its return value will be cached and when it's requested another time it won't be ran again and the cached value will be automatically returned.
The Quail language supports multithreading in two different ways.
The first is the canonical - explicit threading. You can create a thread using Thread class:
th = Thread(function(n) {
through 0:n as i
print(i)
}, [5])
th.start()First argument is a function that will be run asynchronously, the second - list of arguments that will be passed to this function. More info about Thread API is available at Quail Core Docs
The second way to run asynchronous code is to use async keyword. If you add this keyword before some construction (for, while, through, function call, etc.) this line construction will be ran asynchronously. For example, if you want to create a perpetual background loop, you can write:
async while true {
# some background code here
}Also a topic that you won't really face that often. Preprocessor is a small program that goes through all your code before even parsing it and does some modifications. In particular (at the moment of writing) it can insert contents of an another file in executing file and replace text. Here are the examples:
Inserting:
In file code.q
print("Hello")In main file:
#:include code.q
print("Hello 2")After preprocessing, main file contents will be transformed into:
print("Hello")
print("Hello 2")Contents of original file won't be harmed!
Replacement:
#:alias "SAY HELLO" print("Hello")
SAY HELLOWill be transformed into
print("Hello")Also supports regex:
#:alias "(SAY|TELL) HELLO TO (?<name>[a-zA-Z]*)" print("Hello, ${name}")
SAY HELLO TO MarkWill be transformed into
print("Hello, Mark")This chapter will teach you about how values are stored in Quail.
You already know that every value in Quail is an object. So, how are they stored? Consider this example:
a = 2
b = 2We create 2 then put it into a. That's what it looks like, but it's not quite right. Actually the 2 is created and just get thrown into heap. Then, we grab a link to that place in the heap and place the link into a.
In this example a and b contain links to separate objects, but what if:
a = 2
b = aNow guess what? We have only one object in our heap. It's just links that are copied. a and b now store different links to the same object. We can proove it by:
a = 2
b = a
a.somefield = "Test message"
print(b.somefield)As you can see we set some field to some message in a, but we can access it from b, because they refer to the same object. The same way argument passing happens:
function f(obj) {
obj.somefield = "Test message"
}
a = 2
f(a)
print(a.somefield)But what if you want to explicitly copy an object? Then there is a copy function:
a = 2
b = copy(a)
a.somefield = "Test message"
print(b.somefield)Now it will print null, because variables now refer to separate objects.
Now let's consider this example:
a = [1, 2, 3]
b = copy(a)
a[0].somefield = "Test message"
print(b[0].somefield)Run this example. You will see Test message appear. But why? We've copied the list!
The problem is that we've copied just the list. Not the values in it. The list is different, you can check it, but lists in a and b refer to same objects. So to copy an object and all of its contents (e.g. a list or a dict), use clone function:
a = [1, 2, 3]
b = clone(a)
a[0].somefield = "Test message"
print(b[0].somefield)Now we'll see the null.
Congratulations!
You have made it, you've read all the Getting Started's 25 chapters. Now you can consider yourself a pro in Quail programming.
Now I recommend you save Quail Core Docs as a bookmark and look through it to learn about different built-in functions, classes and methods.
If you want to know even more about Quail, then you can proceed reading the Quail Specification
Thank you for using Quail!