Tutorial - Functions and
Prologs in Candle
Introduction
In this tutorial, we'll go through the top-level language constructs in
Candle, the routines and prologs in Candle.
At top-level, there are several important differences between Candle
and XQuery, which highlights the different design philosophy of the two
languages. Candle is designed to be a general-purpose scripting
language, capable of developing large applications; whereas XQuery is
designed to be a focused DSL (domain-specific language). Based on this
contrast, it will be easy to understand the top-level language
differences:
- XQuery adopts an ad-hoc
query style - after the prologs and function definitions, a query
expression follows immediately. Candle adopts a formal programming
style - there's no expression or statements at top-level, and the
execution starts with the designated
main()
routine, like in C/C++ and Java.
- XQuery supports global
variable. Candle does not. This will be further explained in the
following section.
There are 4 types of routine in Candle: expression function, statement
function, template and method. The first two types are covered in this
tutorial, and the other two are covered in following tutorials.
Global Variable vs. Tunneled
Variable
Candle does not support global variable. This is an intentional design.
In an ad-hoc query, like XQuery, global variables can be useful. But in
a large,
complex application, global variables can become unmanageable. This is
also one of the reasons Java does not support global variable.
The other reason that Candle does not support global variable is to
encourage Candle users to adapt a better language facility, i.e. the tunneled variable,
which can make Candle program more adaptive. Say function A calls
function B, they'll see the same global variable value. There's no way
for function A to change the values of the global variables that
function B is going to see, unless we go procedural (which is very bad
practice). But with tunneled variable, that's easy. Function A can
shadow any value on the stack by putting new value on the stack with
the same name before calling B.
The value of tunneled variable in Candle is more than just saving the
trouble of passing parameters around. The other, probably more
important, usage of it is to make routines adaptive or
context-sensitive. When a routine takes value from the tunneled stack,
it actually participates in context
inheritance.
Context inheritance is probably one of the most important things
invented after class inheritance.
Web page designers understood its value and make heavy use of it when
they design CSS stylesheets. With the popularization of XSLT and
Candle, we'll see more creative usage of this feature in the
programming world in the coming years.
Functions in Candle
There are two types of functions on Candle: expression functions and
statement functions, as 'good things come in twos'. The following table
compares two types of function:
|
Expression Function |
Statement Function |
Syntax |
function
name(parameters) as return-type
{ expression
} |
function
name(parameters) { statements;
} |
Return Value |
Expression function
always has return value. |
Statement function does
not return value directly. |
Routine Body |
The body of an
expression function is a single expression, most likely a nested FLWOR
expression. |
The body of statement
function contains many statements. |
Functionality |
An expression function
evaluates an expression and returns its value. |
A statement function
executes some statements, which construct some node output in the
default output document in the context. |
Common
Features |
Two
types of function do share many common features. Though the syntax is
slightly different, but the semantic is almost the same. |
FLWOR expressions and
node construction expression. |
FLWOR statements and
node construction statements. |
Different Features |
|
Some node construction
features are only supported at statement-level, like the apply;
and apply to expr;
statements for applying template transformation. |
While two types of functions might have similar appearance, they have
quite different usage. As you practise more, you should be able to tell
when to use one and when to use the other. Generally, if you are
processing some atomic values, then you should use expression function;
and if you are constructing some nodes, then statement function is
normally more convenient.
There's no absolute barrier between these two types of routines in
Candle, as between method and function. In Candle, following the
principle of separation-of-side-effects,
a function can never call a method. But between these two types of
functions, the separation is just syntactic. They can easily call
each other. The example below is an illustration:
<?csp1.0?>
function format-month-expr(dt) as string {
let month-str =
("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")
return month-str[dt?month]
}
function format-month-stam(dt) {
let month-str =
("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
{ month-str[dt?month] }
}
function format-date-expr(dt) as string* {
(!! we intensionally
calls the format-month statement function
<temp>format-month-stam(dt);</temp>
+ " " + dt?day + ", " + dt?year,
" or ",
!! calling the
format-month expression function
format-month-expr(dt)
+ " " + dt?day + ", " + dt?year)
}
function format-date-stam(dt) {
{format-month-expr(dt)}
" " {dt?day} ", " {dt?year}
" or "
format-month-stam(dt);
" " {dt?day} ", " {dt?year}
}
function main() {
let today = today();
"Today is: " { format-date-expr(today)
} <br/>
"Today is: " format-date-stam(today);
<br/>
}
Try
it yourself
»
Prologs in Candle
There are only are a few types of prologs in Candle. They are:
Prolog Type |
Syntax |
Remarks |
namespace declaration |
namespace default-ns,
prefix1=ns:name, prefix2='uri'; |
The markup
is reserved
for default markup namespace declaration and script is
reserved for default script
namespace declaration. |
import prolog |
import
at 'uri'; |
Import from other
scripts. |
schema declaration |
schema name {
... } |
Defines a node schema.
Shall be covered in details in following tutorial. |
grammar declaration |
grammar name
{ ... } |
Defines a grammar. Shall
be covered in details in following tutorial. |
expression function
declaration |
function name(parameters)
as type
{ expression
} |
Already covered in this
tutorial. |
statement function
declaration |
function name(parameters)
{ statements;
} |
Already covered in this
tutorial. |
template declaration |
template
<pattern> name
(parameters) { statements;
} |
Defines a template.
Shall be covered in details in following tutorial. |
method declaration |
method name(parameters)
as type
{ statements;
} |
Defines a method. Shall
be covered in details in following tutorial. |
Namespace Declaration
In the latest release of Candle, a new qualified name and namespace
notation is introduced. This new notation is similar to Java's
namespace. XML's namespace syntax using URI is still supported for
backward compatibility.
Here's an example of namespace declaration:
namespace ns:default:name:space,
sty=ns:org:candlescript:style,
svg='http://www.w3.org/2000/svg';
ns:default:name:space
is a default namespace
declaration. It must proceed the prefixed namespace declarations.
The new
recommended namespace notation is a
hierarchy
of names, starting with ns
and separated by token ':'. Candle does not mandate domain names
to be used after the top level name ns
,
but for the names to be globally unique, you should do so. The
top level name ns
allows us to easily differentiate fully
expanded name from prefixed name.
Here's an example element in the same document containing the above
namespace declaration:
<foo bar=baz
svg:d=(M,100,50,L,75,110,Z) sty:fee:foe=123>
ns:org:candlescript:style:
qux
</foo>
Qualified names in a markup document are resolved based on the
following simple rules:
- If a name does not
have any
prefix, then it is resolved under the declared default namespace. If
there's no
default namespace declaration, then it
is consider
to be under the
nil
namespace. So in the above example, names foo, bar, M, L, Z
are all
under namespace candle:core
;
- If the name starts with
top-level name
ns
,
then it is a fully expanded Qname, like the qux
in the above example;
- Otherwise, the name is a
prefixed name, and the first name should be one of the prefixes
declared at the beginning of the document, except predefined
prefixes (
nil
and candle
).The baz, d
and foe
in the above example are prefixed names. The baz
is using the predefined prefix nil
.
The above rules apply to any Qname in a document, whether it
is element name, or attribute name, or Qname in the
literal value.
Import Prolog
The import prolog allows you to import routine, grammar and schema
definitions from another script. Note that the definition import is not
transitive. That is if script A imports script B, and script B imports
script C. Definitions in script C are only visible to script B, not
script A. (In current beta release, the URI should only point to a
local file. It cannot be a script from the Internet.)