- PYXTUT - tutorial for the PYX scripting language
First we need the to install node.js - you can download an installer here (also see Instruction for installing node.js on Linux )
Now open a shell, from the command line: Install the pyx shell with the following command
npm install pyxlang -g
(You can always uninstall it later with the command npm uninstall pyxlang -g
)
Run the pyx
program, first thing you see is a command prompt
>
You can enter arithmetic expression, and see the result
> 2+2
4
Or more complicated ones: the sum of the square of three with the square of two (the * sign means multiplication)
> 3*3+2*2
13
or even more complicated ones: First the expression 5-2
is computed, that's because of the brackets. This can be usefull: sometimes you are not quite sure which expression is computed first - the brackets force some order into all of this.
> 3*(5-3)+2*2
10
You can always return to the previous expression with arrow up and to the next expression with the arrow up and down - and change them all over (that's a big deal!)
(the pyx
program is also writing the pyx_history
file, each time that you run a statement successfully it is written onto the end of this file)
One important detail: In the shell it is possible to write a statements in more than one line. This looks as follows
> 2 *
... 3
6
However in the pyx_history
file this will look as follows:
2 *
3
The ...
symbol is only displayed in the interactive shell, it is a sign that the previous line has been continued and that the shell is waiting for more input.
A real program will not have the ...
symbol, it is not part of the text that you are typing.
Now you also have mathematical constants, these are numbers that should never change, like the number Pi and the Euler constant (it looks a bit strange right now, we will learn about maps in a later chapter)
# the constant pi
> mathconst.pi
3.141592653589793
# Euler's constant
> mathconst.e
2.718281828459045
You can ask about stuff, typing help(mathconst)
will explain all about mathconst
In the shell:
> help(mathconst)
# map of mathematical constants.
# the number PI
> mathconst.pi
3.141592653589793
# the Euler constant
> mathconst.e
2.718281828459045
# The square root of two
> mathconst.sqrt2
1.4142135623730951
# Other values:
mathconst.sqrt1_2 # - square root of one half.
mathconst.log2e # - base e logarithm of 2
mathconst.log10e # - base e logarithm of 10
mathconst.log2e # - base 2 logarithm of e
mathconst.log10e # - base 10 logarithm of e
We got some built-in functions: cos
is a function, you can ask cos
to compute the cosine on any number cos(3)
returns the cosine of 3.
> cos(3)
-0.9899924966004454
Of the cosine of pi:
> cos(mathconst.pi)
-1
You have a lot of functions here: press on the TAB key twice and you get the whole list of reserved words and functions
(The TAB key has the following label on the keyboard ->|
)
>
and break continue def elif else false for if none
not or return true use while yield ARGV ENV abs(
atan( cos( each( exists( exit( find( help( int( join( joinl(
keys( lc( len( localtime( map( mapIndex( mathconst max( mid( min(
parseJsonString( pop( pow( print( println( push( random( range( readFile( reduce(
repeat( reverse( sin( sort( split( sqrt( str( system( tan( time(
toJsonString( type( uc( writeFile(
See more information about the built-in functions in this reference
You also have variables: A variable is a name that can be assigned a value (more exactly: it is a name that stands for a location in computer memory where a value is being stored)
> two=2
2
> three=3
3
If you use two
in a mathematical expression, then it's stored value 2
will be used - that's the value that we just assigned to the variable!
> two * three
6
You can make your own function that computes the square of a given number and adds a one to it
> def twoSquarePlusOne(x) x * x + 1
- Here
def
means that we are defining a function twoSquarePlusOne(x)
mean that we are defining the function calledtwoSquarePlusOne
and that the function needs to get a parameter calledx
- when you use the function, then it retuns the following expression, computed with the value of the parameter :
x * x + 1
that means the argument value raised to the power of two and add one to it.
Let's use that function!
Look at twoSquarePlusOne(2)
- here the argument x is set to the parameter variable 2
, and that is used to compute the expression x * x + 1
- meaning 2 * 2 + 1
meaning 5.
> twoSquarePlusOne(2)
5
> twoSquarePlusOne(3)
10
> twoSquarePlusOne(4)
17
You can also use the function call in any arithmetic expression
> num = 2 * twoSquarePlusOne(3)
20
> num - 1
19
First the function is called twoSquarePlusOne(3)
and the result, which is 10, is multiplied by two. The result is 20, that value is stored in variable num
.
The next line takes the value of variable num
and substracts one from it, we get 19.
There are also built-in functions, that come with the language. For examople the built-in function takes the first argument and raises it to the power defined by the second argument.
pow(2,3)
8
Here pow
computes two to the power of three, (that means: two multiplied by two multplied by two)
The same function twoSquarePlusOne
can be written as follows: now it uses the built-in function pow
- this one computes the power of a number
> def twoSquarePlusOne(x) pow(x,2) + 1
> twoSquarePlusOne(2)
9
> twoSquarePlusOne(3)
10
You can get some explanation on the built-in function pow, or look at the PYX function reference - pyxfunc
Or you can use the built-in function help
to tell us about the built-in function pow
> help(pow)
> pow(2,2)
4
> pow(2,3)
8
> pow(2,4)
16
Autocompletion: a big trick - you can write the beginning of a function or variale name and then press the TAB key twice - it will show you the name of all functions that start with wht you just typed. s tab tab
will show you the list of functions that start with the letter s.
(The TAB key has the following label on the keyboard ->|
)
> s
s( sin( sort( split( sqrt( str( system(
if you type si TAB TAB
then there is only one function sin( - so it will just put sin( at the place where you are typing. Believe me, that's a big time saver
There are other keyboard shortcuts in the shell:
Shortcut | What it does |
---|---|
Ctrl-a | Jump to the beginning of the current line |
Ctrl-e | Jump to the end of the current line |
Ctrl-r | Search backwords through the command history |
You can store a sequence of commands in a file lets save the following text in the file named p.p
def pythagoras(sideA, sideB) sqrt( pow(sideA,2) + pow(sideB,2) )
a=3
b=4
println( pythagoras(a, b) )
The statements here are computing the length of the hypothenuse of a right triangle, where the two legs are of length 3 and 4.
To run the program write the following command pyx p.p
pyx p.p
5
You can add the -x
command line option, this traces each statement of the program, while the program is running:
pyx -x p.p
+ a = 3
+ b = 4
pythagoras(sideA=3, sideB=4)
+ pow(3, 2) {
+ 9
}+ pow(4, 2) {
+ 16
}+ sqrt(25) {
+ 5
}+ }
+ println(5) {
5
+ }
You can have a list of the numbers between one and five.
> a=[1,2,3,4,5]
[1,2,3,4,5]
A list is defined by putting [
followed by the values contained in the list, at the end of the list you put in a ]
character.
The values in the list are all separated by a comma.
or get such a list with the range
function
> a=range(1,10)
[1,2,3,4,5,6,7,8,9]
or get a list of the odd numbers between one and twenty (odd numbers do not divide by two)
The third parameter to range is the value that is used as an increment. The value of one is used, if we have only pass two parameter to the range
function.
> a=range(1,20,2)
[1,3,5,7,9,11,13,15,17,19]
One thing you can do is access the first, second and third elements of the list, like this
> a[0]
1
> a[1]
3
> a[2]
5
lets print out all numbers between one and one hundred
> i=0
0
> while i<100 {
... println(i)
... i=i+1
... }
1
2
3
....
100
- First the number 0 is put into variable i
- now we have a
while
statement: this one continues to do the stuff within the brackets { and }, while the variable i is smaller than 100.- Now the stuff in the brackets does two things - it prints out the value of i with
println(i)
and then sets i to the value of i plus one. - lets look at
i=i+1
the right hand sidei+1
takes the old value of i and adds one to it. Now we got a new value, this new value is then stored back into the memory location referred to by variablei
So we take the old value and add one to it, and then put that value back into variablei
.
- Now the stuff in the brackets does two things - it prints out the value of i with
- if you want to group togather more then one statement then you need to put these within the brackets { and }
Now lets get the sum of all squares between one and one hundred
> i=0
0
> sum=0
0
> while i<100 {
... sum=sum+i*i
... i=i+1
... }
> println(sum)
328350
Again: sum=sum+i*i
, first take and compute the square of i*i
and add that result to the old value of sum
, then put the computed value back into variable sum
Or you can make a function that computes the sum of all the squares of numbers for a given range numbers
> def sumOfSquares(from,to) {
... sum=0
... while from<to {
... sum=sum+from*from
... from=from+1
... }
... return sum
... }
> println(sumOfSquares(1,10))
285
> println(sumOfSquares(1,42))
23821
> println(sumOfSquares(1,100))
328350
It is possible to write the same thing as a for
loop.
A for loop is quite similar, you have a number x that is running for every value provided by the range
function.
However you don't have to add one to a variable and check if the loop condition is try, the for loop just runs on all values provided by the range function..
The following statement is run for each of these values.
> def sumOfSquares(from,to) {
... sum=0
... for x range(from,to)
... sum=sum+x*x
... return sum
... }
> println(sumOfSquares(1,10))
285
> println(sumOfSquares(1,42))
23821
> println(sumOfSquares(1,100))
328350
Is a for loop better than a while loop? Depends how you look on it,
- on the one hand a while loop has a lot of flexibility - you are writing the expression that checks if you continue with the loop.
- on the other hand a for loop means less code, less code means fewer opportunities to do something wrong.
It's a kind of trade off - the world of programming has many trade offs...
You can get a list of ten numbers, where all of the numbers are zero. You can use the dim
function:
> lst = dim(10)
[0,0,0,0,0,0,0,0,0,0]
You can also get a list of lists like this:
> lst = dim(3,3)
[[0,0,0],[0,0,0],[0,0,0]]
Each element of the list is another list. Now you can access that as a board for a three in a row game:
> lst[0][0]=1
1
> lst[1][1]=1
1
> lst[2][2]=1
1
> lst
[[1,0,0],[0,1,0],[0,0,1]]
if you want to check what is on the board: lst[0][0]
- first we get the first row lst[0]
then we get the first element of the first row lst[0][0]
You can also initialize a list and assign a value to each of the entries - the dimInit
function does that:
> dimInit({},3)
[{},{},{}]
There is a type of data called a map. It allows to give names to things.
Like the days of the week
You can map between the name of the day and its number:
> dayOfWeekReverse = { "Sunday" : 1, "Monday" : 2, "Tuesday" : 3, "Wednesday" : 4, "Thursday" : 5, "Friday" : 6, "Saturday" : 7 }
{"Sunday":1,"Monday":2,"Tuesday":3,"Wednesday":4,"Thursday":5,"Friday":6,"Saturday":7}
>
> dayOfWeekRevers["Tuesday"]
3
You can also use a number as a key value in a map.
We put the map into a variable called dayOfWeek.
> dayOfWeek = { 1: "Sunday", 2: "Monday", 3: "Tuesday", 4: "Wednesday", 5: "Thursday", 6: "Friday", 7: "Saturday" }
{"1":"Sunday","2":"Monday","3":"Tuesday","4":"Wednesday","5":"Thursday","6":"Friday","7":"Saturday"}
And then show the name of the day of the week - dayOfWeek[6]
shows the name of the sixth day.
> println(dayOfWeek[6])
Friday
You can pass over all of the entries of a map with the for
statement:
> for key, value dayOfWeek
... println("mape entry: {key} - {value}")
mape entry: 1 - Sunday
mape entry: 2 - Monday
mape entry: 3 - Tuesday
mape entry: 4 - Wednesday
mape entry: 5 - Thursday
mape entry: 6 - Friday
mape entry: 7 - Saturday
Now a map with a number key is very different from a list, for example you can have gaps in the key values:
> firstPrimes={ 2: 'two', 3: 'three', 5 : "five", 7 : "seven" }
{"2":"two","3":"three","5":"five","7":"seven"}
> firstPrimes[3]
"three"
You can also use a map to organize your data, like having a list of records for character of the muppet show:
> employees = [ { "Name": "Kermit", "Surname": "Frog", "Profession": "Producer" }, { "Name": "Fozzy", "Surname": "Bear", "Profession": "Comedian" } ]
[{"Name":"Kermit","Surname":"Frog","Profession":"Producer"},{"Name":"Fozzy","Surname":"Bear","Profession":"Comedian"}]
> firstEmployee=employees[0]
{"Name":"Kermit","Surname":"Frog","Profession":"Producer"}
> firstEmployee=employees[0]
> firstEmployee["Name"]
"Kermit"
> secondEmployee=employees[1]
{"Name":"Fozzy","Surname":"Bear","Profession":"Comedian"}
> secondEmployee["Surname"]
"Bear"
You can divide your program into multiple files. That's can be very convenient if the file grows too large, or if you have a function that you want to use in more than one program, without having to copy the text of the function.
Let's write down the following text info file testuse.p
def newComplex(x,y) {
return [x, y]
}
def cadd(a, b) {
return [ a[0] + b[0], a[1] + b[1] ]
}
def cmul(a, b) {
return [ a[0] * b[0] - a[1] * b[1], a[1] * b[0] + a[0] * b[1] ]
}
def cshow(a) {
return "{a[0]}+i{a[1]}"
}
You can now include the text of file testuse.p
and use all the functions that were declared in that file:
> use "testuse.p"
"<function>"
> use "tests/testuse.p"
"<function>"
> a=newComplex(2,3)
[2,3]
> b=newComplex(4,5)
[4,5]
> c=cmul(a,b)
[-7,22]
> cshow(c)
"-7+i22"
An important detail: if you have the PATH
environment variable set, then the statement use "testuse.p"
will search for the file name "testuse.p" in all directories specified by the PATH
environment variable.
There is a second form of the use
statement, the as
keyword will put all symbols into a map/namespace.
See the example, where all the symbols are accessible via the complex
map/namespace
# include the source of another file with the use directive
use "tests/testuse.p" as complex
c = complex.newComplex(2,3)
d = complex.newComplex(4,5)
e = complex.cmul(c, d)
println("complex product: {complex.cshow(e)}")
There are the following operations, these can be used in expressions
Operator | Meaning |
---|---|
+ |
Add two number values, if the arguments are strings then concatenate the strings
|
- | Substract the second argument number from the first argument number. |
* | Multiply two number |
/ | Divide two numbers |
% | Divide two numbers with remainder, divide the first value by the second value and return the remainder of the division. |
and | boolean and - both the left and the right side must be true. If the sub expression is a number - the value of zero counts as false, all other numbers count as true. |
or | boolean or - either the left or the right side must be true. If the sub expression is a number - the value of zero counts as false, all other numbers count as true. |
not | boolean not - gets one parameter, if the parameter is true then the value of false is returned, and vif the parameter is false then the value of true is returned. If the argument expression is a number - the value of zero counts as false, all other numbers count as true. |
< | checks if numberic value of first argument is smaller than the numeric value of the second argument |
<= | checks if numberic value of first argument is smaller than or equal to the numeric value of the second argument |
> | checks if numberic value of first argument is bigger than the numeric value of the second argument |
>= | checks if numberic value of first argument is bigger than or equal to the numeric value of the second argument |
== | Requires both argument to be of equal type. For numbers: checks if numberic value of first argument is equal to the numeric value of the second argument. For strings: checks if two strings are equal. Any other type: checks if two variables refer to the same value. You can also compare with none. |
!= | Requires both argument to be of equal type. For numbers: checks if numberic value of first argument is not equal to the numeric value of the second argument. For strings: checks if two strings are not equal. Any other type: checks if two variables refer to the same value. You can also compare with none. |
Everything else is done as functions (including raising a number to the nth power and bitwise operations)
You can define variables that refer to text. lets define variable a
that refers to the text hello world
and then print that text to the screen with the function println
> a='hello world'
"hello world"
> println(a)
hello world
Working with text is a bit like working with an array - you can access each letter contained within the text like this: (accessing a letter that is outside of the range gives you an error)
> a='123'
"123"
> a[0]
"1"
> a[1]
"2"
> a[2]
"3"
> a[3]
Error: Can't lookup index 3
the text is included within the characters '
You can have multiple lines of text - every string constant can span multiple lines.
> a='hello
... beautiful
... world'
"hello\n beautiful\n world"
> println(a)
hello
beautiful
world
The lines are separated by a newline character - you can either enter a new line or write it as the sequence of characters \n
This here is the same text!
> a='hello\n beautiful\n world'
"hello\n beautiful\n world"
> println(a)
hello
beautiful
world
Now there is also a second form of writing text constants: with the "
character.
Now this form is very different from the previous form. This one is used for writing reports.
You can have sections within the characters {
and }
- these refer to variables in the program.
If this is text is used in a program, then you will see the the following:
> numBottles=10
10
> song="{numBottles} of juice on the wall, take one down and you will have {numBottles-1}"
"10 of juice on the wall, take one down and you will have 9"
> println(song)
10 of juice on the wall, take one down and you will have 9
Now you can write a program that tells you the whole story:
> numBottles=5
5
> while numBottles>1 {
... println("{numBottles} of juice standing on the wall, take one down and you will have {numBottles-1}")
... numBottles = numBottles - 1
... }
5 of juice standing on the wall, take one down and you will have 4
4 of juice standing on the wall, take one down and you will have 3
3 of juice standing on the wall, take one down and you will have 2
2 of juice standing on the wall, take one down and you will have 1
The same program can be written with fewer commands, in this form:
> for numBottles range(5,0,-1)
... println("{numBottles} bottles of juice standing on the wall, take one down and you will have {numBottles-1}")
5 bottles of juice standing on the wall, take one down and you will have 4
4 bottles of juice standing on the wall, take one down and you will have 3
3 bottles of juice standing on the wall, take one down and you will have 2
2 bottles of juice standing on the wall, take one down and you will have 1
1 bottles of juice standing on the wall, take one down and you will have 0
Or you can have a function that receives the number of bottles to write the song - this has a big advantage: you can just call the function and tell it the number of lines you want to print on the screen
> def showBottleSong(maxBottles)
... for numBottles range(maxBottles,0,-1)
... println("{numBottles} of juice standing on the wall, take one down and you will have {numBottles-1}")
"<function>"
> showBottleSong(5)
5 of juice standing on the wall, take one down and you will have 4
4 of juice standing on the wall, take one down and you will have 3
3 of juice standing on the wall, take one down and you will have 2
2 of juice standing on the wall, take one down and you will have 1
1 of juice standing on the wall, take one down and you will have 0
> showBottleSong(3)
3 of juice standing on the wall, take one down and you will have 2
2 of juice standing on the wall, take one down and you will have 1
1 of juice standing on the wall, take one down and you will have 0
You can also concatenate text values with the +
sign
> title="very big"
"very big"
> animal="bear"
"bear"
> title+" "+animal
"very big bear"
But you can't add a text value and a number - that's an error
> lives=9
9
> "cat lives: " + lives
Error: Can't add String to Number
#(1) "cat lives: " + lives
|...............^
You can turn a number into a string with the str
function, now this one is valid:
> "cat lives: " + str(lives)
"cat lives: 9"
The other way round: you can turn a text string into a floating point number
> num('3.141516')
3.141516
> num('.031415e2')
3.1415
Or you can turn a text string into an integer
> int('12345')
12345
Thie can also be used to round floating point values to integers
> int(3.1415)
3
> int("3.1415")
3
A text string that starts with 0x counts as a hexadecimal number (with base 16)
> int('0xFF')
255
Or you can explicitly set the radix/base like this:
> int('FF', 16)
255
There are also other built-in functions with strings
A function that returns the number of chracters in a text
> len('Rivers know this: There is no hurry. We shall get there some day.')
65
Like finding the position of a text within a larger text
> find('Its raining cats and dogs!', 'cats')
12
> find('Its raining cats and dogs!', 'dogs')
21
The function returns -1 when it can't find the text
> find('Its raining cats and dogs!', 'bears')
-1
You can extract the text between the first and the third character of a text - like this
> a='I’m so rumbly in my tumbly.'
"I’m so rumbly in my tumbly."
> mid(a,0,3)
"I’m"
Or get all of the text after the fourth character
> mid(a,4)
"so rumbly in my tumbly."
You can repeat a text three times like this
> repeat('Oh, bother. ', 3)
"Oh, bother. Oh, bother. Oh, bother. "
or change all of the occurances of one string with another one
> b=repeat('Oh, bother. ', 3)
"Oh, bother. Oh, bother. Oh, bother. "
> replace(b,"Oh,", "No", -1)
"No bother. No bother. No bother. "
Of replace just the first occurance of one string with another one
> replace(b,"Oh,", "No", 1)
"No bother. Oh, bother. Oh, bother. "
Or replace the first two occurances like this:
> replace(b,"Oh,", "No", 2)
"No bother. No bother. Oh, bother. "
Sometimes you don't want to find an exact string, instead it is possible to specify a pattern that can match a multitude of possible text values. A regular expression describes a text pattern, you can do some neat tricks with these patterns.
The most simple kind of pattern looks like this /Pooh/
- it matches the string Pooh
> text="So Winnie-the-Pooh went round to his friend Christopher Robin, who lived behind a green door in another part of the forest."
"So Winnie-the-Pooh went round to his friend Christopher Robin, who lived behind a green door in another part of the forest."
> match(text,/Pooh/)
[14,"Pooh"]
The function match
is searching for the regular expression /Pooh/ - and returns the first match, which is at position 14 within the text.
Now You can also search for Either Pooh or Christopher like this:
> matchAll(text,/Pooh|Christopher/)
[[14,"Pooh"],[44,"Christopher"]]
The regular expression /Pooh|Christopher/
matches either the string Pooh
or the string Christopher
, the function matchAll
returns all matches of the regular expression, for each match you get an array with the position of the entry and the text of the match.
> text="asdasd pooh12 aa pooh3423 eee"
"asdasd pooh12 aa pooh3423 eee"
> matchAll(text,/pooh[0-9]+/)
[[7,"pooh12"],[17,"pooh3423"]]
The regular expression /pooh[0-9]+/
is matching the string pooh
that must be followed by one or more digits. [0-9]
matches any digit (any character in the range of 0123456789) and [0-9]+
means that one or more digits are matched.
You can also use regular expressions to modify text
text="Pooh,Bear ## Roo,Kanga ## Christopher,Robin "
"Pooh,Bear ## Roo,Kanga ## Christopher,Robin "
> replacere(text, /([a-zA-Z]+),([a-zA-Z]+)/, "$2;$1", -1)
"Bear;Pooh ## Kanga;Roo ## Robin;Christopher "
The regular expression /([a-zA-Z]+),([a-zA-Z]+)/
is matching any sequence of letters followed by a ,
and then followed by another sequence of letters.
The third parameter to replacere
is telling the function how to substitute a match, here we have $2:$1
what does that mean?
Now look at the group ([a-zA-Z]+)
- the parenthesis mean that this is a group, a sub-expression. The first occurence is referred to as $1
in the replacement string, while the second group is $2
- the replacement string swaps the first and the second group, and places a :
character between them!
You can use regular expression to split up a text into pieces
> text="Roo : Kanga :: Piglet ::: Pooh"
"Roo : Kanga :: Piglet ::: Pooh"
> split(text, /:+/)
["Roo "," Kanga "," Piglet "," Pooh"]
Here the function split
is taken the input text, and splitting it up. The delimiter between the pieces is /:+/
- any sequence of :
characters
> split(text, /\s*:+\s*/)
["Roo","Kanga","Piglet","Pooh"]
Now here the delimiter is a bit more complicated: /\s*:+\s*/
- it is a sequence of zero or more whitespace characters.
Let's look at \s*
, here \s
is standing for a whitespace character and the *
suffix means zero or more of these
:+
means one or more :
characters.
There are a few more options for regular expression, Please see this Regualar expression cheatsheet (The explanation in the link is relevant for javascript as well as the PYX language)
You can run other command line programs, just like this:
> res = system("ls /")
["Applications\nLibrary\nSystem\nUsers\nVolumes\nbin\ncores\ndev\netc\nhome\nopt\nprivate\nsbin\ntmp\nusr\nvar\n",0]
The system
function runs the given command, it returns a list of two values - the output of the program is the first value of the list and the status of the command is the second value
> println(res[0])
Applications
Library
System
Users
Volumes
bin
cores
dev
etc
home
opt
private
sbin
tmp
usr
var
> println(res[1])
0
A function that returns multiple values can also be called like this - here each element of the returned list is assigned a different variable. This is called 'multiple assignment'. Note that you need to have exactly the same number of variables as there are elements in the returned list
> output, status = system('ls /')
["Applications\nLibrary\nSystem\nUsers\nVolumes\nbin\ncores\ndev\netc\nhome\nopt\nprivate\nsbin\ntmp\nusr\nvar\n",0]
>
> println(output)
Applications
Library
System
Users
Volumes
bin
cores
dev
etc
home
opt
private
sbin
tmp
usr
var
> println(status)
0
You also have a second form of writing & running commands - the backtick operator
> out,status = `ls /`
["Applications\nLibrary\nSystem\nUsers\nVolumes\nbin\ncores\ndev\netc\nhome\nopt\nprivate\nsbin\ntmp\nusr\nvar\n",0]
> println(out)
Applications
Library
System
Users
Volumes
bin
cores
dev
etc
home
opt
private
sbin
tmp
usr
var
> print(status)
0
You can combine expression values to form just the command that is needed, very similar to strings with the " delimitor. An expression within { } brackets is evaluated, and the result is inserted into the string that is run in the shell. An expression with backticks can span several lines.
> directory='/'
"/"
> out, status = `ls {directory}`
["Applications\nLibrary\nSystem\nUsers\nVolumes\nbin\ncores\ndev\netc\nhome\nopt\nprivate\nsbin\ntmp\nusr\nvar\n",0]
> println(out)
Applications
Library
System
Users
Volumes
bin
cores
dev
etc
home
opt
private
sbin
tmp
usr
var
> print(status)
0
Both system
and the backtick operator run the process in the default shell ( '/bin/sh' on Unix, process.env.ComSpec on Windows )
Now the built-in variable ENV
is a map, it stands for the environment variables. If you add or remove an entry in ENV
then the changed environment variables will be passed to the processes that are run via system
or the backtick operator.
let's say you have some structured string encoded as text in the json format.
You can make a variable out of it - with the parseJsonString
function
> text='{"persons":[{"id":"323412343123","name":"Michael","surname":"Moser","age":52,"stuff":[3,2,1]}]}'
"{\"persons\":[{\"id\":\"323412343123\",\"name\":\"Michael\",\"surname\":\"Moser\",\"age\":52,\"stuff\":[3,2,1]}]}"
> data=parseJsonString(text)
{"persons":[{"id":"323412343123","name":"Michael","surname":"Moser","age":52,"stuff":[3,2,1]}]}
Now it is much easier to manipulate the data
> data.persons[0].id=123
123
> data.persons[0].age=18
18
> unshift(data.persons[0].stuff,4)
[4,3,2,1]
This is the same as writing (see more in "object based programming")
> data['persons'][0]['id']=123
123
> data['persons'][0]['age']=18
18
> unshift(data['persons'][0]['stuff'],4)
[4,3,2,1]
Now you use the can convert the data back into a json formatted string
> text=toJsonString(data)
"{\"persons\":[{\"id\":123,\"name\":\"Michael\",\"surname\":\"Moser\",\"age\":18,\"stuff\":[4,3,2,1]}]}"
You can do the same with YAML - this is another way of encoding structured data as text (the text as YAML is supposed to be more readable, compared to JSON)
> text=toYamlString(data)
"persons:\n - id: 123\n name: Michael\n surname: Moser\n age: 18\n stuff:\n - 4\n - 3\n - 2\n - 1\n"
> println(text)
persons:
- id: 123
name: Michael
surname: Moser
age: 18
stuff:
- 4
- 3
- 2
- 1
the parseYamlString
function converts the YAML text back into a value of nested maps and arrays
> data = parseYamlString(text)
{"persons":[{"id":123,"name":"Michael","surname":"Moser","age":18,"stuff":[4,3,2,1]}]}
> data
{"persons":[{"id":123,"name":"Michael","surname":"Moser","age":18,"stuff":[4,3,2,1]}]}
Sometimes you don't want to define all parameter values. You can assign a default value to a parameter, and that value will be used if the function call does not pass a variable for that parameter.
> def add(num, inc=1)
... num + inc
"<function>"
the parameter inc
has the default value 1
> add(42)
43
The function is called with only one parameter, the second parameter is inc
and it receives the default value - that value is one.
> add(200,200)
400
Now a second parameer is present in the function call, in this situation the default value is not used.
Sometimes a big problem can be divided into smaller pieces, now the small pieces may also look like the big problem, like here:
Or it's an action that repeats itself again and again, like this picture by M. C. Escher, the left hand is drawing the right one, which is drawing the left one...
You can see more of his paintings of M. C. Escher in this online gallery
Now in programming you can get the same effect, by a function that calls itself.
Let's say you want to compute the product of all numbers from 1 to any given number.
> def factorial(n)
... if n == 1
... 1 else n * factorial(n-1)
"<function>"
> factorial(3)
6
> factorial(4)
24
> factorial(5)
120
The function factorial
takes the argument n
, if it is one, then it returns one, if it is not one then it takes that number and multiplies it with factorial(n-1)
The function factorial
is calling itself, that is called recursion.
Let's say you want to get the factorial of four
factorial(4) == ( 4 * factorial(3)) == (4 * (3 * factorial(2))) === (4 * (3 * (2 * 1)))
You can write down the same thing without recursion.
> def factorial(n) {
... result = 1
... for i range(1,n+1)
... result = result * i
... return result
... }
"<function>"
> factorial(3)
6
> factorial(4)
24
> factorial(5)
120
A second example: lets say you have a list of numbers that are ordered - each element of the list is bigger than the previous one.
> tosearch=[2, 4, 5, 7, 9, 10, 12, 14, 15, 17, 20]
[2,4,5,7,9,10,12,14,15,17]
We want to search this array, and check if the number 12 is part of this list or not.
The approach is to take check if the number in them middle of the list - that's the sixth element tosearch[5] is 10. Now 10 is smaller than 12, that means that we need to look at the list between the sixth and the eleventh element and search that part. Now we are looking at the list toSearch=[10, 12, 14, 15, 17, 20], again take the middle element, with the value 15 - this one is bigger than 12 - so look at the first half.
Let's do the search without recursion.
tosearch=[2, 4, 5, 7, 9, 10, 12, 14, 15, 17]
println("searching:", tosearch)
def search(tosearch, findme) {
low=0
high=len(tosearch)-1
while low <= high {
middle = int( (high + low)/2 )
if tosearch[ middle ] == findme
return true
elif tosearch[ middle ] > findme
high = middle-1
else
low = middle+1
}
return false
}
println("10 ", search(tosearch,10))
println("11 ", search(tosearch,11))
println("12 ", search(tosearch,12))
println("13 ", search(tosearch,13))
println("14 ", search(tosearch,14))
Now the same with recursion
tosearch=[2, 4, 5, 7, 9, 10, 12, 14, 15, 17]
println("searching:", tosearch)
def search(tosearch, findme) {
low=0
high=len(tosearch)-1
while low <= high {
middle = int( (high + low)/2 )
if tosearch[ middle ] == findme
return true
elif tosearch[ middle ] > findme
high = middle-1
else
low = middle+1
}
return false
}
println("10 ", search(tosearch,10))
println("11 ", search(tosearch,11))
println("12 ", search(tosearch,12))
println("13 ", search(tosearch,13))
println("14 ", search(tosearch,14))
Now compute a list of the squares of all numbers between one and 10.
The first step is to define a function square
that computes the square of the number given as argument. note that the last mathematical expression is also computing the value returnd by the function)
> def square(x) x * x
> square(2)
4
> square(3)
9
> square(4)
16
The built-in map
function will call the square
function on all element of the list of numbers from one to 9 - and return a new list with the result. In the returned list each number of the original list is turned into its square!
> map( range(1,10), square)
[1,4,9,16,25,36,49,64,81]
Now lets the compute the sum of all the squares between one and ten
First put that list of squares in a variable - squares
> squares=map( range(1,10), square)
[1,4,9,16,25,36,49,64,81]
now let's get the sum of the squares between one and ten with the built-in reduce
function.
> def sum(x,y) x+y
> reduce(squares, sum, 0)
285
The reduce function calls sum
on the initial value 0 and the first value of the list. Next it calls sum
on the result of the previous step and the second value of the list, and so on.
It would be the same as calling sum( squares[2], sum( squares[1]. sum( squares[0], 0))))
and so on, up until the last element.
You can also have functions that return other functions. now that's a bit tricky:
function anypower
gets the argument variable n.
all it does is to return an unnamed function as return value def(x) pow(x,y)
this function can always use the outer variable n - as it was passed when anypower
was called.
> def anypower(n)
... return def(x) pow(x,n)
Now calling anypower(3)
will return another function that will always compute the power of three.
> powOfThree=anypower(3)
> powOfThree(2)
8
> powOfThree(3)
27
> powOfThree(4)
64
There are a number of almost magical tricks here:
def(x) pow(x,n)
is using the variablen
that is defined outside of that same function - that's because it is nested within theanypower
function, so that the value ofn
becomes part of the environment of the returned function- also see that the function
def(x) pow(x,n)
does not have a name, that's on purpose - it's an anonymous function that is used only as a return value powOfThree=anyposer(3)
- the returned function is stored in variablepowOfThree
. that means that a function is a kind of value, that can hold some captured state in i (this is referring to the value n, that is defined outside of the returned function).powOfThree(3)
- the function stored in the variablepowOfThree
is used as a function.
An now you can use that to compute the table of squares for any number
> map( range(1,10), anypower(2) )
[1,4,9,16,25,36,49,64,81]
> map( range(1,10), anypower(3) )
[1,8,27,64,125,216,343,512,729]
> map( range(1,10), anypower(4) )
[1,16,81,256,625,1296,2401,4096,6561]
And now lets get the sum of the power of three for the numbers between one and one hundred
> reduce( map( range(1,100), anypower(3) ), sum, 0)
24502500
It takes a while to learn all these conceptt. It blew my mind, when I somehow learned all this, believe me!
All this has a big advantage - when dealing with a big program it is easier to think of it in terms of functions,
Assign statements can change all sorts of variables, you can't know which variables have been changed at any given moment, now things become much easier when you only view the progam in terms of functions.
i think that it helps to look at problems from a different perspectives, i think that's the real value of functional programming - even if you don't do that in your day-to-day business, it is important to know that there is a different view on things. I think that this is generally important in life, not just in programming.
Lets say we have a map like this:
> a={"Name": "Pooh", "Surname": "Bear", "Likes": "Friends and Songs and lots of stuff"}
{"Name":"Pooh","Surname":"Bear","Likes":"Friends and Songs and lots of stuff"}
Now you can access an element of the map by the name of a key - just like this:
> a['Name']
"Pooh"
However there is also a shorter way - if the key of the entry looks like an identifier (it means that it doesn't have spaces and only consists of letters digits and underscore characters)
> a.Name
"Pooh"
That has a been added for a reason - to make it easier to use the map in a program.
> def makePoint(posX, posY)
... return { "x": posX, "y": posY }
"<function>"
> p=makePoint(12,20)
{"x":12,"y":20}
> p.x
12
> p.y
20
> p.x+p.y
32
This is a shorter form of using the map, you can now access the fields of the map, as if they were properties of a record or an object.
Now some fields of the map can also be function values
> def makeComplex(re, im) {
... t = {
... "re": re,
... "im": im,
... "add": def(c) {
... return makeComplex(t.re + c.re, t.im + c.im)
... },
... "show": def() {
... println("re: {t.re} im: {t.im}")
... }
... }
... return t
... }
"<function>"
>
Here the makeComplex
function is returning a map with the propertes re
and im
- these are numbers. But the properties add
and show
can work on the properties of the same map - by accessing the captured variable t
. This way we just made an object that acts like a complex number, see here:
> a=makeComplex(2,3)
{"re":2,"im":3,"add":"<function>","show":"<function>"}
> b=makeComplex(4,5)
{"re":4,"im":5,"add":"<function>","show":"<function>"}
> c=a.add(b)
{"re":6,"im":8,"add":"<function>","show":"<function>"}
> c.show()
re: 6 im: 8
Some say that Objects and Closures are equivalent, however this is the subject of a lively debate (see the link ).
Runtime errors can happen in a program, like dividing by zero
./pyx
> value = 42
42
> value / 0
Error: Can't divide by zero
#(1) value / 0
|.......^
Or tring to read a file that does not exist
> textInFile = readFile("noSuchFileExists.txt")
Error: Can't read file: noSuchFileExists.txt error: Error: ENOENT: no such file or directory, open 'noSuchFileExists.txt'
#(1) textInFile = readFile("noSuchFileExists.txt")
|..............^
Still you would like to have the program deal with such errors, somehow. The way to do this is by adding a try / catch
block.
> try {
... a=readFile("noSuchFile.txt")
... println(a)
... } catch ex {
... println( toJsonString(ex) )
... }
try {
a=readFile("noSuchFile.txt")
println(a)
} catch ex {
println( ex.stack )
println( "Fields of the exception map: " + toJsonString(ex) )
}
{"message":"Can't read file: noSuchFile.txt error: Error: ENOENT: no such file or directory, open 'noSuchFile.txt'","stack":"Error: Can't read file: noSuchFile.txt error: Error: ENOENT: no such file or directory, open 'noSuchFile.txt'\n#(2) a=readFile(\"noSuchFile.txt\")\n |...^\n","offset":8,"fileName":[null,"try {\na=readFile(\"noSuchFile.txt\")\nprintln(a)\n} catch
The instructions in the statement between the try
and catch
keywords can throw errors. Now only when an error occurs we get into the statement in the block after the catch
statement.
This block now has a special variable ex
- the exception map. This value holds some more information about the error that just occured.
> try {
... a=readFile("noSuchFile.txt")
... println(a)
... } catch ex {
... println( ex.stack )
... println( "Fields of the exception map: " + toJsonString(ex) )
... }
Error: Can't read file: noSuchFile.txt error: Error: ENOENT: no such file or directory, open 'noSuchFile.txt'
#(2) a=readFile("noSuchFile.txt")
|....^
Fields of the exception map: {"message":"Can't read file: noSuchFile.txt error: Error: ENOENT: no such file or directory, open 'noSuchFile.txt'","stack":"Error: Can't read file: noSuchFile.txt error: Error: ENOENT: no such file or directory, open 'noSuchFile.txt'\n#(2) a=readFile(\"noSuchFile.txt\")\n |....^\n","offset":9,"fileName":[null,"try {\n a=readFile(\"noSuchFile.txt\")\n println(a)\n} catch ex {\n println( ex.stack )\n println( \"Fields of the exception map: \" + toJsonString(ex) )\n}\n"]}
The fields of this variable
- ex['message'] This field holds a message that describes the reason for the error
- ex['stack'] This field is a string, it holds a message that describes the location in the program, where the error occured. If you got to that point by calling a sequence of functions, then it describes each of functions that have been called.
- ex['offset'] The offset in the source file/text expression, where the error occured.
- ex['fileName'] An array with two elements. First comes the file name where the error occured, then the text of that file.
There is a third possible clause in a try/catch block - the finally
statement - this block of statements is run in both the event of an error or if no error occured within the try block!
The for
statement is a bit special.
You can loop over the elements of an existing list
> for a [1,2,3]
... println(a)
1
2
3
You can loop over all entries of a map
> for key,value {'Pooh': 'Bear', 'Piglet': 'Piggy', 'Rooh': 'Kangaroo'}
... println("key: {key} value: {value}")
key: Pooh value: Bear
key: Piglet value: Piggy
key: Rooh value: Kangaroo
However in many cases you don't want to create a whole list or map of entries, just in order to pass over all the elements in that structure.
You always want to create the next element, just when you want to visit it with the for
loop.
Now that's when you need a generator.
Let's look at the followin special function: myrange
in the next example:
> def mygen(from,to) {
... while from < to {
... yield from
... from = from + 1
... }
... }
"<function>"
>
> for a myrange(1,10)
... println("the number is {a}")
Error: undefined variable: myrange
> for n mygen(1,10)
... println("The number is {n}")
The number is 1
The number is 2
The number is 3
The number is 4
The number is 5
The number is 6
The number is 7
The number is 8
The number is 9
The function mygen
is called, it produces the numbers between the argument values from
to to
(not including the value of to
).
Now mygen
is passing a number the for
loop via the yield n
statement. At this moment the mygen
function stops and the statments of the for
loop work with the number they got from the yield
statement. Once the loop has finished, we return from the yield
statement right into the mygen
function and here we are ready to produce hte next value that will be used by the next iteration/pass of the loop.
The mygen
function is called a generator function, because it has a yield
statement. Note that mygen
can't call another function that does the yield
statement, this statement has to be in the generator function itself, in the top level of the generator function.
tbd
Meanwhile look at pyxfunc - in the "Input and Output" section.
tbd
Meanwhile look at pyxfunc - in the "Input and Output" section.
tbd
Meanwhile look at pyxfunc - in the "Input and Output" section.
Now you can do all kinds of stuff, by putting togather all of this.
We have reached the end of our story, now I am wishing you lots of fun with the PYX programming language, may it be of use to you!