7 Elements of Wafl Library

7.1 Program Control

Side Effects

Command line programs often require some outputs to describe the program actions, or some file reading and writing operations which take no explicit place in the program return result. Such things are called side effects. They are not present in the pure functional languages, but can be of significant use in command line programs and scripts.

Some of the functions presented in Command Line Library produce side effects. For example, the input function and all echo functions. These functions are focused on the communication, and side effects control is their secondary task. There are two functions with a primary role of controlling the side effects: return and aside.

Function / Type and Description

return

('1 * '2 -> '2)
Evaluate both arguments, dispose the 1st result and return 2nd one.

aside

('1 * ('1 -> '2) -> '1)
Apply 2nd arg. to 1st, dispose result and return 1st arg:
    aside(x,fn) == fn(x).return(x)

Function return has two arguments. It evaluates both in the given order, and returns the second one. The first one is assumed to have a side effect role, and its value is discarded.

It may be missleading to consider this function in a usual form return(x,y), because it is usually used in a different way. It is designed to be used to provide a post-processing, or to finalize some processing, which performed some side-effects. It is used, almost exclusively, in the form ...exp... .return(y), where expression ...exp... is evaluated and discarded, and then y is evaluated and returned.

"Good morning!"
.return( "Good afternoon!" )

Good afternoon!

The function return is useful after a file writing, directory traversing and some other side-effecting operations, when there is no need to preserve or return the specific results. For example, the following expression does some processing and then disposes the current result and returns message “OK” as a confirmation that program has finished:

... some processing ...
.echoTxt('--------\n')
.return( "OK" )

...whatever the processing output is...
--------
OK

"Good morning!"
.echoLn()
.echoTxt( '-------------------\n' )
.return( "Good afternoon!" )

Good morning!
-------------------
Good afternoon!

Function return is not always the best solution, because it discards not only the result of the first argument but also may discard an error report, so please use it with care.

Function aside is similar to return - it evaluates two arguments in the given order, but then returns the first one and discards the second one. It assumes that the current result is still needed, but performs some intermediate processing. It is typically used to create a report on the current progress or result state, like in the following example:

"Good morning!"
.aside(\x: 
    ("Before saying: \"" + x + "\", bring a cup of coffee first!").echoLn()
)

Before saying: "Good morning!", bring a cup of coffee first!
Good morning!

In some simple cases it is often sufficient (and better) to use echo functions (the echoFn in the previous example), but if the reporting includes a file creation or a database update, then aside is a much better solution.

Function aside can be used to prepare some temporary resources for the processing or to perform a cleaning after the processing. For example, consider the following code:

... begin ...
.aside(\x: ... create some tmp files ... )
... processing ...
.aside(\x: ... delete tmp files ... )
... finalize ...

Iteration Functions

After discussing the list processing, now we have a broader picture of usual programming techniques in functional programming languages. The computation flow is implicitly controlled by some higher order functions and by the processed data itself. This approach is general and flexible enough to support solving virtually any problem. However, in some cases, the creation and handling of additional lists burdens the computation without real need.

This is why Wafl core library includes some so called iterative functions. Iterative functions are used as a tool for controlling the iterative computation, but based on integers and conditions, rather than on sequences.

Function / Type and Description

iterate

('1 * Int * Int * ('1 * Int -> '1) -> '1)
Left associative folding of specified integers range:
    iterate(zero,2,4,fn) = fn(fn(fn(zero,2),3),4)
        = zero.fn(2).fn(3).fn(4)
        = (2..4).foldl(fn,zero)

iterateBy

('1 * Int * Int * Int * ('1 * Int -> '1) -> '1)
Left associative folding of specified integers range by step:
    iterateBy(zero,2,6,2,fn) = fn(fn(fn(zero,2),4),6)
        = zero.fn(2).fn(4).fn(6)
        = [2,4,6].foldl(fn,zero)

repeatUntil

('1 * ('1 -> '1) * ('1 -> Bool) -> '1)
Repeat the function evaluation until the condition is reached:
    repeatUntil(x,fn,cond) =
        if cond(x) then x
        else fn(x).repeatUntil(fn,cond)

Function iterate(zero,from,to,fn) computes a left associative folding of the specified integer range. It is functionally equivalent to imperative for-loop, like the next one in C++:

auto result = zero;
for( int i=from; i<=to; i++ )
    result = fn( result, i );
return result;

Function iterate(zero,from,to,fn) is equivalent to expression (from..to).foldl(fn,zero), but provides the computation without creation of the list (from..to), so it is more efficient. If the range is in the decreasing order, then the negative step is used:

{#
    iterate( '#', 1, 10, \s,x: s + '-' + x.asString() ),
    iterate( '#', 10, 1, \s,x: s + '-' + x.asString() ),
    (1..10).map(sum(1,_)),
    (1..10).map(fact)
#}
where {
    sum(n,m) = iterate( 0, n, m, operator+ );
    fact(n) = iterate( 1, 1, n, operator* );
}

{# '#-1-2-3-4-5-6-7-8-9-10', '#-10-9-8-7-6-5-4-3-2-1', [1, 3, 6, 10, 15, 21, 28, 36, 45, 55], [1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800] #}

Function iterateBy(zero,from,to,step,fn) is a more general form of the iterate. When iterate is used, the step is always 1 or -1. With iterateBy the step may be any integer.

{#
    iterateBy( '#', 1, 10, 2, \s,x: s + '-' + x.asString() ),
    iterateBy( '#', 10, 1, -3, \s,x: s + '-' + x.asString() )
#}

{# '#-1-3-5-7-9', '#-10-7-4-1' #}

Function repeatUntil(x,fn,cond) provides the iteration control based on the given condition. It repeats iterative transformation of x until the condition is met. Again, it may be useful to describe that in imperative manner:

auto result = x;
while(!cond(c))
    x = fn(x);
return x;

In the next example the given number is multiplied by itself until the result is greater or equal to 1000:

(2..10).map( enlarge )
where {
    enlarge(n) = n.repeatUntil(
        operator*(n,_),
        operator>=(_,1000)
    );
}

[1024, 2187, 1024, 3125, 1296, 2401, 4096, 6561, 1000]

7.2 File Reading

Function / Type and Description

fileRead

(String -> String)
Read complete file to a string.

fileReadPart

(String * Int * Int -> String)
Read a part of the file, from given 0-based position and with given length in bytes:
    fileReadPart( fname, pos, bytelen )

fileSize

(String -> Int)
Returns the file size.

The fileRead(filepath) function reads the file and returns its contents as a string. The complete file is read, regardless the size. This approach should not be used for very large files, due to the high memory usage.

For very large files, the fileReadPart(filepath,pos,bytelen) function should be used. It reads a file part, beginning form the given position (in bytes) and of the given length.

To determine a file size, the fileSize(filepath) function can be used.

fileRead( 'src/txt/en/title.md' )


# Wafl Tutorial

*Wafl* is a strongly typed functional programming language, with implicit type inference. It was initially designed as an experimental application of FP languages in Web development. However, few years later, Wafl became a simple and very fast scripting language, with a simple and efficient interface to databases.

Wafl Tutorial presents the language constructions, command line interpreter usage, elements of the core library and a lot of examples.

Welcome to *Wafl*!

*Saša Malkov*

fileReadPart( 'src/txt/en/title.md', 100, 100 )

inference. It was initially designed as an experimental application of FP languages in Web developme

fileSize( 'src/txt/en/title.md' )

516

7.3 File Writing

Function / Type and Description

fileWrite

(String * String -> String)
Write string to a file and truncate old content:
    fileWrite( fname, content )

fileWriteAppend

(String * String -> String)
Write string to a file by appending to the file end.
    fileWriteAppend( fname, content )

fileWriteTo

(String * String -> String)
Write string to a file and truncate old content:
    fileWriteTo( content, fname )

fileWriteAppendTo

(String * String -> String)
Write string to a file by appending to the file end.
    fileWriteAppendTo( content, fname )

The function fileWrite(filename,s) writes the given string s to the file and discards the old file contents, if any. The fileWriteTo(s,filename) does the same, only with the changed order of the arguments. It is good to have both functions, because of the dot-syntax.

The functions fileWriteAppend and fileWriteAppendTo have the same arguments, but appends the given string s to the file end, and do not discard the previous file contents.

Each of the functions returns the written string s as a result.

{#
    fileWrite( "tmp.txt", "One sentence. " ),
    fileWrite( "tmp.txt", "Another sentence. " )
#}

{# 'One sentence. ', 'Another sentence. ' #}

fileRead( 'tmp.txt' )

One sentence. Another sentence. 

{#
    fileWrite( "tmp.txt", "One sentence. " ),
    fileWriteAppend( "tmp.txt", "Another sentence. " )
#}

{# 'One sentence. ', 'Another sentence. ' #}

fileRead( 'tmp.txt' )

7.4 File Operations

Function / Type and Description

fileExists

(String -> Bool)
Check if exists a file with the given name.

fileOrDirExists

(String -> Bool)
Check if exists a file or a directory with the given name.

fileDelete

(String -> Bool)
Delete a file.

fileNewTempName

( -> String)
Get a new temporary file name.

The functions fileExists and fileOrDirExists check and return if there is a file or directory with the given name.

The fileExists checks only the files and if it returns true, then there is a file with the given name. It can be used to check before reading or deleting a file.

{#
    fileExists( 'tmp.txt' ),
    fileExists( 'tmp2.txt' ),
    fileDelete( 'tmp.txt' ),
    fileExists( 'tmp.txt' )
#}

{# true, false, true, false #}

The fileOrDirExists checks only both the files and directories and if it returns true, then there is a file or a directory with the given name. It can be used to check before creating a file or a directory.

The function fileNewTempName() returns a candidate name for a new temporary file. The filename is located in the temporary files directory, which depends on the platform.

7.5 Directory Operations

Function / Type and Description

dirCreate

(String -> Bool)
Create a directory.

dirDelete

(String -> Bool)
Delete a directory, if it is empty

dirExists

(String -> Bool)
Check if exists a directory with the given name.

dirFiles

(String -> List[String])
Creates a list of all files satisfying the given filter.

dirSubdirs

(String -> List[String])
Creates a list of all subdirectories satisfying the given filter.

The functions dirFiles(filter) and dirSubdirs(filter) return lists of files and directories that satisfy the given filter. Returned paths are relative to the working directory.

"Directories:\n  "
+ dirSubdirs( '*' ).strJoin('\n  ')
+ "\nFiles:\n  "
+ dirFiles( '*' ).strJoin('\n  ')

Directories:
  .vscode
  bld
  code
  design
  resources
  src
Files:
  bldall.cmd
  bldsite.cmd
  pandocHtml.cmd
  pandocPdf.cmd
  prepmd.cmd
  site.cmd
  test.wafl
  todo.notes.txt
  Tutorial - Site Version.lnk
  Tutorial - Standalone Version.lnk
  WaflTutorial.md

The function dirExists is similar to fileExists and fileOrDirExists, but it checks only if a directory exists with the specified name. It can be used before creating, deleting or traversing a directory.

7.6 Regex Functions

Regex support in Wafl uses ECMAScript regular expressions syntax, as defined and implemented in C++ standard library from C++11.

By default, all quantifiers are greedy (they take as many characters as possible). This can be overridden to ungreedy (to take as few characters as possible) by adding a question mark (?) after the quantifier.

Match the Whole String

Function / Type and Description

regexMatch

(String * String -> Bool)
Check if a string matches given regular expression.

regexMatchI

(String * String -> Bool)
Check if a string matches given regular expression. Ignore letter case.

Regex matching functions regexMatch(s,r) and regexMatchI(s,r) check if the given string s matches the given regular expression r. The matching is performed on the whole string s.

{#
    s.regexMatch("ab.*AB"),
    s.regexMatch(".*ab.*AB.*"),
    s.regexMatch(".*ab.*cd.*"),
    s.regexMatch(".*AB.*ab.*"),
    s.regexMatchI(".*AB.*ab.*")
#}
where {
    s = "abcabcABCABC";
}

{# false, true, false, false, true #}

Match the Substrings

Function / Type and Description

regexPos

(String * String -> Int)
Find the first regex matching position in the given string.

regexPosI

(String * String -> Int)
Find the first regex matching position in the given string. Ignore case.

regexPosAll

(String * String -> List[Int])
Find all regex matching positions in the given string.

regexPosAllI

(String * String -> List[Int])
Find all regex matching positions in the given string. Ignore case.

Regex functions regexPos(s,r) and regexPosI(s,r) look for the first position of a substring of the given string s that matches the given regular expression r.

{#
    s.regexPos("ab.*AB"),
    s.regexPos("ab.*cd"),
    s.regexPos("AB.*ab"),
    s.regexPosI("AB.*ab")
#}
where {
    s = "abcabcABCABC";
}

{# 0, -1, -1, 0 #}

Regex functions regexPosAll(s,r) and regexPosAllI(s,r) look for the starting positions of all substrings of the given string s that match the given regular expression r.

{#
    s.regexPosAll("ab.*AB"),
    s.regexPosAll("ab.*cd"),
    s.regexPosAll("AB.*ab"),
    s.regexPosAllI("AB.*ab"),
    s.regexPosAllI("AB.*?ab"),
    s.regexPosAllI("a.*c"),
    s.regexPosAllI("a.*?c")
#}
where {
    s = "abcabcABCABC";
}

{# [0], [], [], [0], [0, 6], [0], [0, 3, 6, 9] #}

Replace the Substrings

Function / Type and Description

regexReplace

(String * String * String -> String)
Replace all matching of a regex with the given string.

regexReplaceI

(String * String * String -> String)
Replace all matching of a regex with the given string. Ignore case.

Regex functions regexReplace(s,r,x) and regexReplaceI(s,r,x) look for the positions of all substrings that match the given regular expression r, and replace them with the given string x.

{#
    s.regexReplace("ab.*AB", "#"),
    s.regexReplace("ab.*?AB", "#"),
    s.regexReplace("ab.*cd", "#"),
    s.regexReplace("AB.*ab", "#"),
    s.regexReplaceI("AB.*ab", "#"),
    s.regexReplaceI("AB.*?ab", "#"),
    s.regexReplaceI("a.*c", "#"),
    s.regexReplaceI("a.*?c", "#")
#}
where {
    s = "abcabcABCABC";
}

{# '#C', '#CABC', 'abcabcABCABC', 'abcabcABCABC', '#C', '#c#C', '#', '####' #}

Searching

Function / Type and Description

regexSearch

(String * String -> List[String])
Search for the regex matching in the given string.

regexSearchI

(String * String -> List[String])
Search for the regex matching in the given string. Ignore letter case.

regexSearchAll

(String * String -> List[List[String]])
Search for all regex matchings in the given string.

regexSearchAllI

(String * String -> List[List[String]])
Search for all regex matchings in the given string. Ignore letter case.

Regex search functions regexSearch(s,r) and regexSearchI(s,r) look for the first substrings of the given string s that matches the given regular expression r, and return all matched sub-elements. The returned value is a list of strings. The first element is the complete matched substring, and the following elements correspond to the regular expression sub-patterns.

The following example searches for patterns <name>=<n>:

{#
    s.regexSearch("([a-z]+)\\s*=\\s*([0-9]+)")
#}
where {
    s = "a=12 ;bcd = 24; ef =354";
}

{# ['a=12', 'a', '12'] #}

The first element of the returned list is the first matched substring. The second element is the corresponding name part, and the third one is the corresponding n part of the matched substring.

The functions regexSearchAll and regexSearchAllI do the same thing, but for all matching substrings. Thus, the result is not single list, but a list of the string lists - a list for each matched substring:

{#
    s.regexSearchAll("([a-z]+)\\s*=\\s*([0-9]+)")
#}
where {
    s = "a=12 ;bcd = 24; ef =354";
}

{# [['a=12', 'a', '12'], ['bcd = 24', 'bcd', '24'], ['ef =354', 'ef', '354']] #}

7.7 Command Line Library

Functions for command line support are specific for command line Wafl interpreter. They are usually not available in other versions. The main purpose is to support the terminal read and write operations.

7.7.1 Program Arguments

Command line program interpreter supports the command line program arguments. There are some functions and special operator for working with command line arguments:

Function / Type and Description

$*

List[String]
List of command line arguments.

$#

Int
A number of command line arguments.

cmdLineArgs

( -> List[String])
Return a list of command line arguments.

cmdLineArgsCount

( -> Int)
Return a number of command line arguments.

The ‘$*’ constant is used to get the list of all command line arguments. The synonym, in a form of a function, is the function cmdLineArgs(). For example, if a program is started using the following command:

clwafl program.wafl a b c "abcde"

Both $* and cmdLineArgs() will return:

['program.wafl', 'a', 'b', 'c', 'abcde']

The number of argument can be computed as the size of the command line arguments list (size($*)) or by using the constant $#, or its synonym function cmdLineArgsCount().

7.7.2 User Interaction

Command Line Output

Command line output is handled by echo functions.

Function / Type and Description

echo

('1 -> '1)
Write the argument’s string representation to the console and return the argument:
    echo(x) == echoTxt( x, x.asString() )

echoLn

('1 -> '1)
Write the argument’s string representation and new line to the console and return the argument:
    echoLn(x) == echoTxt( x, x.asString() + '\n' )

echoTxt

('1 * String -> '1)
Write the second argument to the console and return the first argument.

echoFn

('1 * ('1 -> String) -> '1)
Apply the function to the 1st arg. and write the result to the console:
    echoFn( x, fn ) == echoTxt( x, fn(x) )

Each of the echo functions outputs something to standard console output and returns its first argument.

The echo(x) function outputs the given value x converted to a string, and returns the original value x. The echoLn(x) does the similar, but outputs a new line symbol at the end.

"This is a string"
.echo()
.echoLn()

This is a stringThis is a string
This is a string

The functions echoTxt and echoFn write some other strings to the command line output, but return the first argument in the same way as echo and echoLn. The echoTxt(x,s) writes s and returns x. The echoFn(x,fn) writes fn(x) and returns x:

"This is a string"
.echoTxt("Output from echoTxt\n")
.echoFn(\x: 'Output "' + x + '" from echoFn\n' )

Output from echoTxt
Output "This is a string" from echoFn
This is a string

Command Line Input

Function / Type and Description

input

(String -> String)
Write a string to the console, wait for input and return it.

The input(s) function writes the given string s to the command line output, waits for the user to enter a line to the console, and returns the entered string.

"Hello "
+ input( "What is your name? " )
+ "!"
What is your name? Marvin
Hello Marvin!

7.7.3 Shell Command Execution

Function / Type and Description

cmdExecute

(String -> String)
Execute the command in the active console and return the result.

cmdShellExecute

(String -> String)
Open new shell, execute the command in it and return the result.

cmdLastError

( -> Int)
Get integer code of last command status.

The functions cmdExecute(cmd) and cmdShellExecute(cmd) execute the given shell command string and return the results. The difference between the two is that cmdExecute uses the current shell, while cmdShellExecute opens a new shell. In some cases they may return different results, depending on the shell and specific commands.

The function cmdLastError() returns the exit code of the last executed shell command. As usual, if everything is ok, it returns zero.

7.8 Web and HTTP Functions

Wafl library HTTP and Web functions are organized in two parts:

Universal HTTP Functions

Function / Type and Description

httpGetSize

(String -> Int)
Get WWW content length using HTTP/HTTPS HEADER method.

httpGet

(String -> String)
Get WWW content using HTTP/HTTPS GET method.

httpGet_callback

(String * (Int * Int -> Int) -> String)
Get WWW content using HTTP/HTTPS GET method, with progress callback

The httpGet(uri) function sends a GET HTTP request with a given uri , waits for a response and returns the response content. It can be used for both HTTP and HTTPS requests.

httpGet( 'http://www.informatics.rs/~smalkov/wafl/index.html' )
.size()

15360

The httpGetSize(uri) function sends a HEADER HTTP request with a given uri , waits for a response and returns the reported response content size. It can be used for both HTTP and HTTPS requests. If a server does not react on HEADER request, the function returns -1. If a server reacts on the request but returns an invalid header or a header with no content size data, the function returns -2.

httpGetSize( 'http://www.informatics.rs/~smalkov/wafl/index.html' )

-1

The httpGet_callback(uri,fn) function sends a GET HTTP request with a given uri, uses the fn function of type (Int * Int -> Int) to handle intermediate callbacks, waits for a response and returns the reported response content. The result value should be the same as in the case of the httpGet. The fn(received,expected) can perform some intermediate processing, like progress reporting. The received argument is the count of the total received bytes, and expected argument is the count of the total expected bytes. The expected should be the same as the result of the httpGetSize(uri), and can be a negative value if the server does not provide the expected size.

httpGet_callback( 
    'http://www.informatics.rs/~smalkov/wafl/index.html',
    \r,t: t.echoTxt( r.asString() + ' / ' + t.asString() + '\n')
)
.size()

2628 / -1
4040 / -1
5452 / -1
6864 / -1
8276 / -1
9688 / -1
11100 / -1
12512 / -1
13924 / -1
15336 / -1
15360 / -1
15360

httpGet_callback( 
    'http://poincare.matf.bg.ac.rs/~smalkov/images/h2.png',
    \r,t: t.echoTxt( r.asString() + ' / ' + t.asString() + '\n')
)
.size()

1158 / 7024
3982 / 7024
5394 / 7024
6806 / 7024
7024 / 7024
7024

Web Functions

Function / Type and Description

Form

( -> Map[String][String])
Get form variable set.

FormValue

(String -> String)
Get value of a given form variable.

Session

( -> Map[String][String])
Get session variable set.

SessionValue

(String -> String)
Get value of a given session variable.

Service

( -> Map[String][String])
Get service variable set.

ServiceValue

(String -> String)
Get value of a given service variable.

The functions Form(), Session() and Service() return the corresponding web variable sets - the current form content received from a client, the current set of session variables and the current service configuration. The functions FormValue(varname), SessionValue(varname) and ServiceValue(varname) return the value of a variable with the given variable name.

Function / Type and Description

httpHost

( -> String)
Get HTTP host for current request.

httpPathInfo

( -> String)
Get HTTP path info for current request.

httpScript

( -> String)
Get HTTP script for current request.

The functions httpHost(), httpPathInfo() and httpScript() return the corresponding parts of the currently processed URI.

Function / Type and Description

ask

(String -> Map[String][String])
Ask a question by sending the given page content to the client. Returns the next client’s request.

answerAction

( -> String)
Automatic action URI generator for forms in questions.

The functions ask(pageContent) sends the given page content to the client and waits for the returned answer. All the links and the form actions on the page, that begin with a relative link generated by answerAction(), will represent the alternative answers for the given page and will return to this specific function. The answerAction() will return a result that identifies the specific current program evaluation, so the program may be continued after an appropriate answer is received from the client.

7.9 Wafl Program Evaluation

Function / Type and Description

cmdWafl

(String * List[String] -> String)
Run Wafl program file in a command line shell with given arguments and returns the result. All arguments are passed to Wafl program. No arguments apply to interpreter. The output is redirected to the caller’s output stream.

cmdWaflSrc

(String * List[String] -> String)
Run Wafl program source in a command line shell with given arguments and returns the result. All arguments are passed to Wafl program. No arguments apply to interpreter. The output is redirected to the caller’s output stream.

cmdWaflX

(String * RecordX[]['1] -> String)
Run Wafl program file in a command line shell with given options and returns the result. Options are specified as a record fields:
    cmdLineArgs - the list of command line arguments to pass
        to the program (List[String]);
        coutFile - the full filename of the output redirection file;
        if '*null*' is specified, output is discarded;
        if empty string is specified (default),
        the caller's output stream is used .

These functions do the evaluation of Wafl programs. The cmdWafl(prgpath,args) evaluates a Wafl program in the given file. The cmdWaflSrc(src,args) evaluates the Wafl program written in the string. Both function send the given arguments as command line arguments to the evaluated program, and return the programs output and result as a return value.

cmdWaflSrc('2+3',[])

5

"A controlled error:\n" 
+ cmdWaflSrc( 'an error',[])

A controlled error:
--- Loading from command line
Parser error
    Unexpected text at the end of the file! [err 1201:6]
    Line 1: an error
           ___/
--- End loading from command line

Each Wafl program evaluation is performed in a separate evaluation environment but in the same process. That means that the most of the errors will not propagate to the caller-program, but if the memory usage becomes too high or the process fails, then it will have consequences on the caller-program.

It is even possible that such an evaluated program evaluates another Wafl program:

cmdWaflSrc( 'cmdWaflSrc("2+3",[])',[])

5