Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1542 lines (1320 sloc) 46.885 kB

The core language

While Opa empowers developers with numerous technologies, Opa is first and foremost a programming language. For clarity, the features of the language are presented through several chapters, each dedicated to one aspect of the language. In this chapter, we recapitulate the core constructions of the Opa language, from lexical conventions to data structures and manipulation of data structures, and we introduce a few key functions of the standard library which are closely related to core language constructions.

Read this chapter to find out more about:

  • syntax;

  • functions;

  • records;

  • control flow;

  • loops;

  • patterns and pattern-matching;

  • modules;

  • text parsers.

Note that this chapter is meant as a reference rather than as a tutorial.

Lexical convention

Opa accepts standard C/C++/Java/JavaScript-style comments:

Comments
// one line comment
/*
  multi line comment
*/
/*
  nested
  /* multi line */
  comment
*/

A comment is treated as whitespace for all the rules in the syntax that depend on the presence of whitespace.

It is generally a good idea to document values. Documentation can later be collected by the opadoc tool and collected into a cross-referenced searchable document. Documentation takes the place for special comments, starting with /**.

Documentation
/**
 * I assure you, this function does lots of useful things!
 * @return 0
**/
zero() = 0
Caution
In progress

(Soon, a hyperlink to the corresponding chapter)

Ill-formed documentation comments do not break the compilation, they only break the documentation.

Basic datatypes

Opa has 3 basic datatypes: strings, integers and floating point numbers.

Integers

Integers literals can be written in a number of ways:

x = 10 // 10 in base 10
x = 0xA // 10 in base 16, any case works (0Xa, 0XA, Oxa)
x = 0o12 // 10 in base 8
x = 0b1010 // 10 in base 2

Floats

Floating point literal can be written in two ways:

x = 12.21
x = .12 // one can omit the integer part when the decimal part is given
x = 12. // and vice versa
x = 12.5e10 // scientific notation

Strings

In Opa, text is represented by immutable utf8-encoded character strings. String literals follow roughly the common C/Java/JavaScript syntax:

x = "hello!"
x = "\"" // special characters can be escaped with backslashes

Opa features string insertions, which is the ability to put arbitrary expressions in a string. This feature is comparable to string concatenations or manipulation of format strings, but is generally both faster, safer and less error-prone:

x = "1 + 2 is {1+2}" // expressions can be embedded into strings between curly braces
                     // evaluates to "1 + 2 is 3"
email(first_name,last_name,company) =
  "{String.lowercase(first_name)}.{String.lowercase(last_name)}@{company}.com"
my_email = email("Darth","Vader","deathstar") // evaluates to "darth.vader@deathstar.com"

More formally, the following characters are interpreted inside string literals:

characters meaning

{

starts an expression (must be matched by a closing })

"

the end of the string

\\

a backslash character

\n

the newline character

\r

the carriage return character

\t

the horizontal tabulation character

\\{

the opening curly brace

\\}

the closing curly brace

\'

a single quote

\"

a double quote

\ anything else

forbidden escape sequence

Datastructures

Records

The only way to build datastructures in Opa is to use records. Since they are the only datastructure available, they are used pervasively and there is a number of syntactic shorthands available to write records concisely.

Here is how to build a record:

x = {} //  the empty record
x = {a=2; b=3} //  a record with the field "a" and "b"
x = {a=2; b=3;} //  you can add a trailing semicolon
x = {a=2 b=3} //  all the semicolons are optional
x = {`[weird-chars]` = "2"} //  a record with a field "[weird-chars]"

//  now various shorthands
x = {a} //  means {a=void}
x = {a b=2} //  means {a=void b=2}
x = {~a b=2} //  means {a=a b=2}
x = ~{a b} //  means {a=a b=b}
x = ~{a b c=4} //  means {a=a b=b c=4}
x = ~{a={b} c} //  means {a={b=void} c=c}, NOT {a={b=b} c=c}

The characters allowed in fields names are the same as the ones allowed in identifiers, which is described here.

You can also build record by deriving an existing record, i.e. creating a new record that is the same an existing record except for the given fields.

x = {a=1 b={c="mlk" d=3.}}
y = {x with a=3} //  means {a=3; b=x.b}
y = {x with a=3 b={e}} //  you can redefine as many fields as you want
                       //  at the same time (but not zero) and even all of them

//  You can also update fields deep in the record
y = {x with a.c = "po"} //  means {x with a = {x.a with c = "po"}}
                        //  whose value is {a=1 b={c="po" d=3.}}

//  the same syntactic shortcuts as above are available
y = {x with a} //  means {x with a=void}, even if it is not terribly useful
y = {x with ~a} //  means {x with a=a}
y = ~{x with a b={e}} //  means {x with a=a b={e}}

Tuples

Opa features syntactic support for pairs, triples, etc. — more generally tuples, ie, heteregenous containers of a fixed size.

x = (1,"mlk",{a}) //  a tuple of size 3
x = (1,"mlk") //  a tuple of size 2
x = (1,) //  a tuple of size 1
         //  note the trailing comma to differentiate a 1-uple
         //  from a parenthesized expression
         //  the trailing comma is allowed for any other tuple
         //  although it makes no difference whether you write it or not
         //  in these cases
//  NOT VALID: x = (), there is no tuple of size 0

Tuples are standard expressions: a N-tuple is just a record with fields f1, …, fN. As such they can be manipulated and created like any record:

x = (1,"hello")
do @assert(x == {f1 = 1; f2 = "hello"})
do @assert(x.f1 == 1)
do @assert({x with f2 = "goodbye"} == (1,"goodbye"))

Lists

Opa also provides syntactic sugar for building lists (homogenous containers of variable length).

x = [] //  the empty list
x = [3,4,5] //  a three element list
y = [0,1,2|x] //  the list consisting of 0, 1 and 2 on top the list x
              //  ie [0,1,2,3,4,5]

Just like tuples, lists are standard datastructures with a prettier syntax, but you can build them without using the syntax if you wish. The same code as above without the sugar:

x : list = {nil}
x : list = {hd=3 tl={hd=4 tl={hd=5 tl={nil}}}}
x : list = {hd=0 tl={hd=1 tl={hd=2 tl=x}}}

Identifiers

In Opa, an identifier is a word matched by the following regular expression:
([a-zA-Z_] [a-zA-Z0-9_]* | ` [^`\n\r] `)
except the following keywords:
_, as, do, else, if, match, then, type, with.

In addition to these keywords, a few identifiers that can be used as regular identifiers in most situations but will be interpreted in some contexts:
and, begin, css, db, end, external, forall, import, package, parser, rec, server, val, xml_parser.
It is not advised to use these words as identifiers, nor as field names.

Any identifier may be written between backquotes: x and `x` are strictly equivalent. However, backquotes may also be used to manipulate any text as an identifier, even if it would otherwise be rejected, for instance because it contains white spaces, special characters or a keyword. Therefore, while 1\+2 or match are not identifiers, `1\+2` and `match` are.

Bindings

At toplevel, you can define an identifier with the following syntax:

one = 1
`hello` = "hello"
_z12 = 1+2
Tip

The compiler will warn you when you define a variable but never use it. The only exception is for variables whose name begins with _, in which case the compiler assumes that the variable is named only for documentation purposes.
As a consequence, you will also be warned if you use variables starting with \_.
And for code generation, preprocessing or any use for which you don’t want warnings, you can use variables starting with __.

Of course, local identifiers can be defined too, and they are visible in the following expression:

two =
  one = 1 // an optional semicolon can be put after
  one + one

two =
  one = 1; // the exact same thing as above
           // can be used to make the code less ambiguous
  one + one

two =
  one = 1 // NOT VALID: syntax error because a local declaration
          // must be followed by an expression

Functions

Defining functions

In Opa, functions are regular values. As such, the follow the same naming rules as any other value. In addition, and a few syntactic shorcuts are available:

f(x,y) = // defining function f with the two parameters x and y
  x + y + 1

two =
  f(x) = x + 1 // functions call be defined locally, just like other values
  f(1)

f = x, y -> x + y + 1 // the exact same thing as above
                      // on the left of the arrow, you have comma separated parameters

// you can write functions in a currified way concisely:
f(x)(y) = x + y + 1
// or equivalently
f = x -> (y -> x + y + 1)
Caution

Note that there must be no space between the function name and its parameters, and no spaces between the function expression and its arguments.

f () = f() // NOT VALID: does not parse
x = f () // NOT VALID: parsed as
         // x = f
         // ()

Partial applications

From a function with N arguments, we may derive a function with less arguments by partial application:

add(x,y) = x+y
add1 = add(1,_) // which means add1(y) = add(1,y)
x = add1(2) // x is 3
Caution

Side effects of the arguments are computed at the site and time of partial application, not each time the function is called:

loop() = loop()
add1 = add(loop(), _) // this loops right now
                      // not when calling add1

All the underscores of a call are regrouped to form the parameters of a unique function in the same order are the corresponding underscores:

max3(x,y,z) = max(x,max(y,z))
positive_max = max3(0,_,_) // means positive_max(x,y) = max(0,x,y)

More definitions

We have already seen one way of defining anonymous functions, but there are two. The first way allows to functions of arbitrary arity:

add = x, y -> x + y

The second syntax allows to define only functions taking one argument, but it is more convenient in the frequent case when the first thing that your function does is match its parameter.

add1 =
| 0 -> 1
| 1 -> 2
| 2 -> 3
| _ -> error("Wow, that's outside my capabilities")

This last defines a function that does a pattern matching on its first argument (the meaning of this construct is described in Pattern-Matching).

add1(fresh) =
  match fresh with
  | 0 -> 1
  | 1 -> 2
  | 2 -> 3
  | _ -> error("Wow, that's outside my capabilities")

Operators

Since operators in Opa are standard functions, these two declarations are equivalent:

x = 1 + 2
x = `+`(1,2)

To be used as an infix operator, an identifier must contain only the following characters:

+ \ - ^ * / < > = @ | & !

Since operators as normal functions, you can define new ones:

`**` = Math.pow_i
x = 2**3 // x is 8

The priority and associativity of the operators is based on the leading characters of the operator. The following table show the associativity of the operators. Lines are ordered by the priority of operators, slower operators first.

leading characters associativity

| @

left

|| ?

right

&

right

= != > <

left

+ - ^

left

* /

left

Caution

You cannot put white space as you wish around operators:

x = 1 - 2 // works because you have whitespace before and after the operator
x = 1-2 // works because you have no whitespace before and no white space after
x = 1 -2 // NOT VALID: parsed a unary minus

Type coercions

There are various reasons for wanting to put a type annotation on an expression:

  • to document the code;

  • to avoid value restriction errors;

  • to make sure that an expression has a given type;

  • to try to pinpoint a type error;

  • to avoid anonymous cyclic types (by naming them).

The following demonstrates a type annotation:

x = [] : list(int)

Note that parameters of a type name may be omitted:

x = [] : list(list) // means list(list('a))

Type annotations can appear on any expression (but also on any pattern), and can also be put on bindings as shown in the following example:

x : list(int) = [] // same as s = [] : list(int)
f(x) : list(int) = [x] // annotation of the body of the function
                       // same as f(x) = [x] : list(int)

Grouping

Expressions can be grouped with parentheses or equivalently with begin … end:

x = (1 + 2) * 3
x =
  if i_like_pascal then begin
    "begin end rocks"
  end else (
    "i like parens better"
  )

Modules

Functionalities are usually regrouped into modules.
The syntax resembles the one of record:

List = {{
  empty = []
  cons(hd,tl) = ~{hd tl}
}}

By opposition to records, modules do not offer any of the syntactic shorthands: no ~\{{x}}, no \{{x}}, nor any form of module derivation: no \{\{List with cons(hd,tl) = [hd,hd|tl]}}.
On the other hand, the content of a modules are not field definitions, but bindings. This means that the fields of a module can access the other fields:

m = {{
  x = 1
  y = x // x is in scope
}}

r = {
  x = 1
  y = x // NOT VALID: x is unbound
}

Note that, by opposition to the toplevel, modules contain only bindings, no type definitions.

The bindings of a module are all mutually recursive (but still subject to the recursion check, once the recursivity has been reduced to the strict necessary):

m = {{
  x = y
  y = 1
}}

This will work just fine, because this is reordered into:

m = {{
  y = 1
  x = y
}}

where you have no more recursion.

Caution

Since the content of a module is recursive, it is not guaranteed that the content of a module is executed in the order of the source.

Sequencing computations

In Opa the toplevel is executed, and so you can have expressions at the toplevel:

do println("Executed!")

The do construct can be used to compute an expression and discard its result.

x =
  do println("Dibbs!") // cleaner than saying _unused_name = println("Dibbs!")
                       // but equivalent (almost, see the warning section)
  do println("Aww...")
  1

Note that the definitions of identifiers and the do construct are always followed by an expression in expressions, but never at the toplevel.

Datastructures manipulation and flow control

The most basic way of deconstructing a record is to dot (or "dereference") the content of an existing field.

x = {a=1 b=2}
do @assert(x.a == 1)
c = x.c // NOT VALID: type error, because x does not have a field c

Note that the dot is defined only on records, not sums. For sums, something more powerful is needed:

x = {true} : bool
do @assert(x.true) // NOT VALID: type error

To deconstruct both records and sums, Opa offers pattern-matching. The general syntax is:

do
  match <expr> with
  | <pattern_1> -> <expr_1>
  | <pattern_2> -> <expr_2>
  ...
  | <pattern_n> -> <expr_n>
  end

Note that keywords with and end are optional, as well as |. For clarity, it is however generally a good idea to restrain from omitting them.

When evaluating this extract, the result of <expr> is compared with <pattern_1>. If both match, i.e. the have the same shape, <expr_1> is executed. Otherwise, the same result is compared with <pattern_2>, etc. If no pattern matches, then an error happens and the execution flow stops.

The specific case of pattern matching on boolean can be abreviated using a standard if-then-else construct:

do if 1 == 2 then
     println("Who would have known that 1 == 2?")
   else
     println("That's what I thought!")

// if the else branch is omitted, it default to void
do if 1 == 2 then
     println("Who would have known that 1 == 2?")

// or equivalently
do match 1 == 2 with
   | {true} -> println("Who would have known that 1 == 2?")
   | {false} -> println("That's what I thought!")
   end
Caution

Since the match construct can end with end, beware of its interaction with begin:

do
  begin
    match 0 with
    | _ -> void
  end

This is a syntax error because the end is actually part of the match construct, and so the begin has no corresponding end.

Tip

The same way that f(x,_) means (roughly) y → f(x,y), _.label is a shorthand for x → x.label, which is convenient when combined with higher order:

l = [(1,2,3),(4,5,6)]
l2 = List.map(_.f3,l) // extract the third elements of the tuples of l
                      // ie [3,6]

Patterns

Generally, patterns appear as part of a match … with construct. However, they may also be used at any place where you bind identifiers.

Syntactically, patterns look like a very limited subset of expressions:

1 // an integer pattern
-2.3 // a floating point pattern
"hi" // a string pattern, no embedded expression allowed
{a=1 ~b} // a (closed) record pattern, equivalent to {a=1 b=b}
[1,2,3] // a list pattern
(1,"p") // a tuple pattern
x // a variable pattern

On top of these constructions, you have

{a=1 ...} // open record pattern
_ // the catch all pattern
<pattern> as x // the alias pattern
{~a=<pattern>} // a shorthand for {a=<pattern> as a}
<pattern> | <pattern> // the 'or' pattern
                      // the two sub patterns must bind the same set of identifiers

When the expression match <expr> with <pattern> → <expr2> | … is executed, <expr> is evaluated to a value, which is then matched against each pattern in order until a match is found.

Matching rules

The rules of pattern-matching are simple:

  • any value matches pattern _;

  • any value matches the variable pattern x, and the value is bound to identifier x;

  • an integer/float/string matches an integer/float/string pattern when they are equal;

  • a record (including tuples and lists) matches a closed record pattern when both record have the same fields and the value of the fields matches the pattern component-wise;

  • a record (including tuples and lists) matches an open record pattern when the value has all the fields of the pattern (but can have more) and the value of the common fields matches the pattern component-wise;

  • a value matches a pat as x pattern when the value matches pat, and additionally it binds x to the value;

  • a value matches a or pattern is one of the value matches one of the two sub patterns;

  • in all other cases, the matching fails.

Caution
Pattern-matching does not test for equality

Consider the following extract:

x = 1
y = 2
do match y with
  | x -> println("Hey, 1=2")
  | _ -> println("Or not")

You may expect this code to print result "Or not". This is, however, not what happens. As mentioned in the definition of matching rules, pattern x matches any value and binds the result to identifier x. In other words, this extract is equivalent to

x = 1
y = 2
do match y with
  | z -> println("Hey, 1=2")
  | _ -> println("Or not")

If executed, this would therefore print "Hey, 1=2". Note that, in this case, the compiler will reject the program because it notices that the two patterns test for the same case, which is clearly an error.

A few examples:

list_is_empty(l) =
  match l with
  | [] -> true
  | [_|_] -> false

// and without the syntactic sugar for lists
// a list is either {nil} or {hd tl}
head(l) =
  match l : list with
  | {nil} -> @fail
  | ~{hd ...} -> hd
Warning
At the time of this writing, support for or patterns is only partial. It can only be used at the toplevel of the patterns, and it duplicates the expression on the right hand side.
Warning
At the time of this writing, support for as patterns is only partial. In particular, it cannot be put around open records, although this should be available soon.
Warning

A pattern cannot contain an expression:

is_zero(x) = // works fine
match x with
| 0 -> true
| _ -> false

// wrong example
zero = 0
is_zero(x)
match x with
| zero -> true
| _ -> false
// does not work because the pattern defines zero
// it does not check that the x is equal to zero
Caution

You cannot put the same variable several times in the same pattern:

on_the_diagonal(position) =
  match position with
  | {x=value y=value} -> true
  | _ -> false
// this is not valid because you are trying to give the name value
// to two values

// this must be written
on_the_diagonal(position) =
  position.x == position.y

Loops

At this stage, you may wonder about how to write loops, iterators, etc. in Opa.

Surprisingly, Opa does not offer a specific syntax for loops. Rather, Opa offers function loops as part of the standard library.

// printing a chain 10 times
// repeat has type : int, (-> void) -> void
do repeat(10,(-> println("Hello!")))

// printing the integer for 1 to 10
// inrange has type int, int, (int -> void) -> void
do inrange(1,10,(i -> println("{i}")))

// summing integer starting from 1 until the sum is greater than 50
// while has type: 'state, ('state -> ('state, bool)) -> 'state
~{sum ...} =  // we only return the sum, ie the first field of the pair
   while({sum=0 i=1},
         (~{sum i} ->
            sum = sum + i
            i = i + 1
            ~{sum i}, (sum <= 50)))

// the same function with the for function
// for has type: 'state, ('state -> 'state), ('state -> bool) -> 'state
~{sum ...} =
  for(
    {sum=0 i=1}, // the initial state
    (~{sum i} -> {sum=sum+i i=i+1}), // the function that computes the next state
    (~{sum ...} -> sum <= 50) // the function that tells if we should continue
  )

/* the equivalent with an imperative syntax:
sum = 0

for (i = 1; sum <= 50; i=i+1) {
    sum=sum+i
}
*/

In the above,

  • assignments sum=0; i=1 correspond to the record \{sum=0 i=1} above;;

  • the body of the loop sum=sum+i; i=i+1 corresponds
    to the function ~{sum i} → \{sum=sum+i; i=i+1};

  • the loop condition sum ⇐ 50 corresponds to ~{sum …} → sum ⇐ 50.

Additional loop functions may be easily created, either by building them from these functions, or through Recursion.

Parser

Opa features a builtin syntax for building text parsers, which are first class values just as functions. The parsers implement parsing expression grammars, which may look like regular expressions at first but do not behave anything like them.

An instance of a parser:

sign_of_integer =
  parser "-" [0-9]+ -> {negative}
       | [0-9]+ -> {positive}

A parser is composed of a disjunction of <list-of-subrules> (→ <semantic-action>)?, separated by a |.
When the semantic action is omitted, it defaults to the text that was parsed by the left hand side.

A subrule consists of:

  1. an optional binder that names the result of the subrule. it can be:

    1. x= to name the result x

    2. ~ only when followed by a long identifier. In the case, the name bound is the last component. For instance, ~M.x* means x=M.x*

  2. an optional prefix modifier (! or &) that lookahead for the following subrule in the input

  3. a basic subrule

  4. an optional suffix modifier (?, *, +), that alters the basic in the usual way

And the basic subrule is one of:

"hello {if happy then ":)" else ":("}" // any string, including strings
                                       // with embedding expressions
'hey, I can put double quotes in here: ""' // a string inside single quotes
                                            // (which cannot contain embedded expressions)
Parser.whitespace // a very limited subset of expression can be written inline
                  // only long identifiers are allowed
                  // the expression must have the type Parser.general_parser
{Parser.whitespace} // in curly braces, an arbitrary expression
                    // with type Parser.general_parser
. // matches a (utf8) character
[0-9a-z] // character ranges
         // the negation does not exist
[\-\[\]] // in characters ranges, the minus and the square brackets
         // can be escaped
( <parser_expression> ) // an anonymous parser
Caution

Putting parentheses around a parser can change the type of the parenthesized parsers:

parser x=.* -> ... // x as type list(Unicode.character)
parser x=(.*) -> ... // x has type text

This is because the default value of a parenthesized expression is the text parsed.
This is the only way of getting the text that was matched by a subrule.

A way to use a parser (like sign_of_integer) to parse a string is to write:

Parser.try_parser(sign_of_integer,"36")

For an explanation of how parsing expression grammars work, see http://en.wikipedia.org/wiki/Parsing_expression_grammar.
Here is an example to convince you that even if it looks like a regular expression, you can not use them as such:

parser "a"* "a" -> void

The previous parser will always fail, because the star is a greedy operator in the sense that it matches the longest sequence possible (by opposition with the longest sequence that makes the whole regular expression succeed, if any):
"a"* will consume all the "a" in the strings, leaving none for the following "a".

Recursion

By default, toplevel functions and modules are implicitely recursive at toplevel, while local values (including values defined in functions) are not.

f() = f() // an infinite loop

x =
  f() = f() // NOT VALID: f is unbound
  void

x =
  rec f() = f() // now f is visible in its body
  void

f() = g() // mutual recursion works without having
g() = f() // to say 'rec' anywhere at toplevel

x =
  rec f() = g() // local mutually recursive functions must
  and g() = f() // be defined together with a 'rec' 'and'
                // construct
  void

Recursion is only permitted between functions, although you can have recursive modules if it amounts to valid recursion between the fields of the module:

m = {{
  f() = m2.f()
}}
m2 = {{
  f() = m.f()
}}

This is valid, because it amounts to:

rec m_f() = m2_f()
and m2_f() = m_f()
m = {{ f = m_f }}
m2 = {{ f = m2_f }}

Which is a valid recursion.

Opa also allows arbitrary recursion (in which case, the validity of the recursion is checked at runtime), but it must be indicated explicitely that is what is wished for with val:

rec val sess = Session.make(callback)
and callback() = do something with sess
Tip
The compiler will not allow you to put val when not necessary (i.e. on functions).

Please note that the word val is meant to define recursive values, but not meant to define cyclic values:

rec val x = [x]

This definition is invalid, and will be rejected (statically in this case despite the presence of the val because it is sure to fail at runtime).

Of course, most invalid definitions will be only detected at runtime:

rec val x = if true then x else 0

Directives

Many special behaviours appear syntactically as directives, starting with a @. A directive can impose arbitrary restrictions on its arguments.
They are usually used because we want to make it clear in the syntax that something special is happening, that we do not have a regular function call.

Some directives are expressions, while some directives are annotations on bindings, and they do not appear in the same place.

do if true then void else @fail // @fail appears only in expressions
@expand `=>`(x,y) = not(x) || y // the lazy implication
                                // @expand appears only on bindings
                                // and precedes them

Here is a full list of (user-available) expression directives, with the restriction on them:

@assert

Takes one boolean argument
Raises an error when its argument is false. The code is removed at compile time when the option --no-assert is used.

@fail

Takes an optional string argument
Raises an error when executing (and show the string if any was given) Meant to be used when something cannot happen

@todo

Takes no argument
Behaves like @fail except that a warning is shown at each place when this directive happens (so that you can conveniently replace them all with actual code later)

@toplevel

Takes no argument, and must be followed by a field access
@toplevel.x allows to talk about the x defined at toplevel, and not the x in the local scope

@unsafe_cast

Takes one expression
This directive is meant to bypass the typer. It behaves as the identity of type 'a → 'b.

Here is a full list of (user-available) bindings directives, with the restriction on them:

@comparator

Takes a typename
Overrides the generic comparison for the given type with the function annotated.

@deprecated

Takes one argument of the following kind: {hint = string literal} / {use = string literal}
Generates a warning to direct users of the annotated name. The argument is used when displaying the warning (at compile time).

@expand

Takes no argument, and appears only on toplevel functions.
The directive calls to this function will be macro expanded (but without name clashes). This is how the lazy behaviour of &&, \|\| and ? is implemented.

@private

Takes no argument, only on toplevel binding or module fields.
The identifier tagged is not visible outside the module (or package if at toplevel).

@public

Takes no argument, only on toplevel binding or module fields.
The identifier tagged is visible outside the current module/package (this is the default).

@stringifier

Takes a typename
Overrides the generic stringification for the given type with the function annotated:

@stringifier(bool) to_string(b: bool) = if b then "true" else "false"

Foreign function interface

Foreign functions, or system bindings, are standard expressions. To use one, simply write the key (see the corresponding chapter) of your binding between %%:

x = (%% BslPervasives.int_of_string %%)("12") // x is 12

Separate compilation

At the toplevel only, you can specify information for the separate compilation:

package myapp.core // the name of the current package
import somelib.* // which package the current package depends on

Inside the import statement, you can have shell-style brace and glob expansion:

import graph.{traversal,components}, somelib.*
Tip
The compiler will warn you whenever you import a non existing package, or if one of the alternatives of a brace expansion matches nothing, or a if a glob expansion matches nothing.

Beware that the toplevel is common to all packages.
As a consequence, it is advised to define packages that export only modules, without other toplevel values.

Type expressions

Type expressions are used in type annotations, and in type definitions.

Basic types

The three data types of Opa are written int, float and string, like regular typenames (except that these names are actually not valid typenames).
Typenames can contain dots without needing to backquote them: Character.unicode is a regular typename.

Record types

The syntax for record type works the same as it does in expressions and in patterns:

x : {useless} = @fail // means {useless:void}
x : ~{a b} = @fail // means {a:a b:b}, where a and b are typenames
x : ~{list} = @fail // means the same as {list:list}
                    // this is valid in coercions because you can omit
                    // the parameters of a typename (but not in type definitions)
x : ~{int} = @fail // means {int:`int`}, so you must have defined a type `int`
                   // for this definition to be valid
x : {a b ...} = {a b c} // you can give only a part of the fields in type annotations

Tuple types

The type of a tuple actually looks like the tuple:

(a,b) : (int,float) = (1,3.4)

List types

There is no syntax for the type of list, you simply use list.

x : [int] = [1] // NOT VALID
x : list(int) = [1]

Sum types

Now, record expressions do not have records type (in general), they have sum types, which are simply unions of record types:

x : {true} / {false} = {true}
x : {true} / ... = {true} // sum types can be partially specified, just like record types

Type names

Types can be given names, and of course you can refer to names in expressions:

x : list(int) = [1] // the parameters of a type are written just like a function call
x : bool = 1 // except that when there is no parameter, you don't write empty parentheses
x : list = [1] // and except that you can omit all the parameters of a typename altogether
               // (which means 'please fill up with fresh variables for me')

Variables

Variables begin with an apostrophe except _:

x : list('a) = []
x : list(_) = [] // _ is an anonymous variable

Function types

Function types are written the same way as anonymous functions:

max3 : int, int, int -> int = x, y, z -> max(x, max(y, z))

Type definitions

A type definition allows to give a name to a type.

It simply consists of an identifier, a list of parameters, a set of directives and a body.
Since type definitions can only appear at the toplevel, and the toplevel is implicitely recursive, all the type definitions of a package are mutually recursive.

Here are the most common types in opa as defined in the standard library:

type void = {} // naming a record
type bool = {true} / {false} // naming a type sum
type option('a) = {none} / {some:'a} // a parameterized definition of a type sum
type list('a) = {nil} / {hd:'a tl:list('a)} // a recursive and parameterized
                                            // definition of a type sum

In addition to type expressions, the body of a type definition can be an external types, ie types that represent foreign objects, used when interfacing with other languages.

type continuation('a) = external

Type directives

There are currently only two directives that can be put on type definitions, and they both control the visibility of the type.

The first one is @abstract, which hides the implementation of a type to the users of a library:

package test1
@abstract type Test1.t = int
Test1 = {{
  x : t = 1
}}

Abstracting forces the users to go through the interface of the library to build and manipulate values of that type.

package test2
import test1
x = Test1.x + 1 // this is a type error, since in the package test2
                // the type Test1.t is not unifiable with int anymore

The second directive is @private, which is a type that is not visible from the outside of the module (not even its name). When a type is private, values with that type cannot be exported

package test1
@private type t = int
Test1 = {{
  x : t = 1 // will not compile since the module exports
            // Test1.x that has the private type t
}}

Formal description

This syntax recapitulates the syntactic constructs of the language.

Conventions

The following conventions are adopted to describe the grammar. The following defines program with the production prod.

program ::= prod

A reference to the rule program between parens:

( <program> )

A possibly empty list of <rule> separated by <sep> is written:

<rule>* sep <sep>

A non empty list of <rule> separated by <sep> is written:

<rule>+ sep <sep>

The opa language

A source file is a <program>, defined as follows:

program ::= <declaration>* sep ;?
declaration ::=
  | <type-definition>
  | <binding>
  | <do>
  | <package-declaration>
  | <package-import>

The rules related to separate compilation:

package-declaration ::=
  | package <package-ident>
package-import ::=
  | import <package-expression>* sep ,
package-expression ::=
  | { <package-expression>* sep , }
  | <package-expression> *
  | <package-ident>
package-ident ::= [a-zA-Z0-9_.\-]

Some rules used in many places:

field ::= <ident>

literal ::=
  | <integer-literal>
  | <float-literal>
  | <string-literal>

long-ident ::=
  | <long-ident> . <ident>
  | <ident>

The syntax of the types:

type-definition ::=
  | <type-directive>* type <type-bindings>
type-bindings ::=
  | <type-binding>* sep and
type-binding ::=
  | <type-ident> ( <type-var>* sep "," ) = <type-def>
type-def ::=
  | <type>
  | external
type ::=
  | <type>* sep , -> <type>
  | <tuple-type>
  | <record-type>
  | <type-ident> ( <type>* sep , )
  | int
  | string
  | float
  | <type> / <type>+ sep / <sum-type-end>?
  | <type> <sum-type-end>
  | _
  | <type-var>
  | forall ( <type-var>* sep , ) . <type>
  | ( <type> )

<tuple-type> ::=
  | ( <type> , )
  | ( <type> , <type>+ sep , ,? )

record-type ::=
  | ~? { <record-type-field>* sep ;? <record-type-end>? }
record-type-field ::=
  | ~ <field>
  | <field> : <type>
record-type-end ::=
  | ...
  | <row-var>

sum-type-end ::=
  | ...
  | <col-var>
type-ident ::= <long-ident>

The syntax of the patterns:

pattern ::=
  | <literal>
  | <record-pattern>
  | <tuple-pattern>
  | <list-pattern>
  | <pattern> as <ident>
  | <pattern> | <pattern>
  | <pattern> : <type>
  | ( <pattern> )
  | _

<tuple-pattern> ::=
  | ( <pattern> , )
  | ( <pattern> , <pattern>+ sep , ,? )

<record-pattern> ::=
  | ~? { ...? }
  | ~? { <record-pattern-field>+ sep ;? ;? ...? }
<record-pattern-field> ::=
  | ~? <field> <coerce>?
  | ~? <field> <coerce>? = <pattern>

<list-pattern> ::=
 | [ pattern+ sep , | pattern ]
 | [ pattern* sep , ]

The syntax of the bindings:

binding ::=
  | rec <rec-binding>+ sep and
  | <non-rec-binding>

non-rec-binding ::=
  | <binding-directive> <ident-binding>
  | <binding-directive> <val-binding>

rec-binding ::=
  | <ident-binding>
  | val <val-binding>

ident-binding ::=
  | <ident> <params>+ <coerce>? = <expr>
  | <ident> <coerce>? = <expr>

params ::=
  | ( <pattern>* sep "," )

val-binding ::=
  | <pattern> = <expr>

do ::=
  | do <expr>

coerce ::=
  | : <type>

The syntax of the expressions, except the parsers:

expr ::=
  | <do> ;? <expr>
  | <binding> ;? <expr>
  | match <pattern> with? |? <rule>* sep |? end?
  | if <expr> then <expr> else <expr>
  | if <expr> then <expr>
  | <record>
  | <module>
  | <tuple>
  | <list>
  | <grouping>
  | <expr> : <type>
  | <literal>
  | <expr-or-underscore> ( <expr-or-underscore>* sep "," )
  | <expr-or-underscore> . field
  | <directive>
  | <lambda>
  | <expr-or-underscore> <op> <expr-or-underscore>
  | - <expr>
  | <sysbinding>
  | <ident>
  | <parser>

lambda ::=
  | <pattern>* sep , -> <expr>
  | | <rule>*

rule ::=
  | <pattern> -> <expr>

grouping ::=
  | ( <expr> )
  | begin <expr> end

record ::=
  | ~? { <record-field>* sep ;? }
  | ~? { <expr> with <record-field>+ sep ;? }
record-field ::=
  | ~? <field> <coerce>?
  | <field> <coerce>? = <expr>

tuple ::=
  | ( <expr> , )
  | ( <expr>, <expr>+ sep , ,? )

list ::=
  | [ <expr>+ sep , | <expr> ]
  | [ <expr>* sep , ]

module ::=
  | {{ <non-rec-binding>+ sep ;? }}

expr-or-underscore ::=
  | <expr>
  | _

The syntax of the parsers:

parser ::=
  | parser <parser-rules>
parser-rules ::=
  | <parser-rule>+ sep |
parser-rule ::=
  | <parser-prod>+ -> <expr>
  | <parser-prod>+
parser-prod ::=
  | <parser-name>? <subrule-prefix>? <subrule> <subrule-suffix>?
parser-name ::=
  | <ident> =
  | ~
subrule-prefix ::=
  | &
  | !
subrule-suffix ::=
  | *
  | +
  | ?
subrule ::=
  | <character-set>
  | .
  | <string-literal>
  | { <expr> }
  | <long-ident>
------------------------
Jump to Line
Something went wrong with that request. Please try again.