Using the API
Note
To try the examples in this file, start an interactive Python prompt. See Starting Python for details. The outputs shown come from a session on the C++ backend.
Importing Curry Modules
Curry modules can be imported into Python via the import keyword. To avoid
ambiguity with Python modules, Curry code is imported from a virtual package called
curry.lib. We will see an example shortly.
To import Peano it is first necessary to configure the search path residing
at curry.path. The initial value of this comes from the environment
variable CURRYPATH. If your CURRYPATH does not already contain the current
directory, you can add it by saying:
>>> curry.path.insert(0, '.')
Change the list in place, as here. An assignment to curry.path rebinds
the name in the package. The search path of the interpreter stays as it
was. The same holds for curry.flags.
Now, import Peano:
>>> from curry.lib import Peano
This compiles the code of Peano into executable form, loads it into a
Python module, and registers that object with Python.
Whenever a Curry module is imported, a reference to it is stored in
curry.modules. We can see that it now contains the implict module
Prelude as well as Peano:
>>> sorted(curry.modules)
['Peano', 'Prelude']
curry.import_() imports a module by name and returns it:
>>> DL = curry.import_('Data.List')
A dotted name imports the packages on its way, so this adds the package
Data to curry.modules as well. curry.import_('Data') returns
the package. A source file is imported with
curry.import_('Peano.curry', is_sourcefile=True). A list of names
imports each of them. The products of a module are written beside its
source under .curry/sprite-pakcs-3.4.1/. They are the ICurry
(Peano.icy), the interfaces of the front end (Peano.fint,
Peano.icurry) and the JSON. The generated C++ and the shared object
(Peano.so) are added when the module has been compiled.
A module loaded already is returned as it was loaded. Within one process its
source is not read again: when the file changed after the import,
curry.import_() returns the loaded module and logs a warning that
names the module and the file, once per edit. An import of another file of
the same module name (is_sourcefile=True) returns the loaded module as
well, and the warning names both files. A new process reads the edited or
the other file. On the C++ backend the background compile of a module (the
flag interpret, Status) compiles the source as it is when the
compile runs; an object compiled from an edited source is refused at the
swap, so the code of a loaded module never changes without a load.
Inspecting Symbols
Peano exposes public symbols (i.e., constructors and functions) as Python
attributes. These can be accessed in the usual way:
>>> Peano.S
<curry constructor 'S'>
>>> Peano.O
<curry constructor 'O'>
>>> Peano.add
<curry function 'add'>
Three rules say what a module offers. The public functions and
constructors are attributes under their Curry names, operators included. A
private function is not an attribute; curry.symbol reaches it by its
full name. A type is not an attribute; see
Inspecting Types below.
These objects contain a wealth of information. For example:
>>> Peano.add.name
'add'
>>> Peano.add.fullname
'Peano.add'
>>> Peano.add.signature
'Nat -> Nat -> Nat'
>>> Peano.add.scheme
<Scheme Peano.add :: Nat -> Nat -> Nat>
The signature is the type scheme of the symbol in Curry syntax. Sprite reads
it from the FlatCurry interface that the front end wrote beside the module.
Peano.add.scheme is the same scheme as an object; see
curry.typecheck. A symbol of the Prelude with a class constraint
shows its context:
>>> curry.symbol('Prelude.+').signature
'Num a => a -> a -> a'
Peano.add.info is the info table of the symbol in the runtime: its
name, arity, tag, and step function.
To see the ICurry and the generated code, try the following commands:
>>> from curry import inspect
>>> print(inspect.geticurry(Peano.add))
add:
var $1
var $2
$1 <- $0[0]
$2 <- $0[1]
case $1 of
Peano.O -> return $2
Peano.S _ ->
var $3
$3 <- $1[0]
return ICCall('Peano.S', IFCall('Peano.add', $3, $2))
>>> print(Peano.add.getimpl())
/****** Peano.add ******/
...
inspect.geticurry reads the ICurry of a module loaded from its compiled
code from the file beside it. getimpl gives the generated C++
function of a compiled module, and the bytecode listing of a module that
the runtime interprets, which a freshly imported module is under the
default of the flag interpret (see Interpreter flags).
Tip
Use print when examining long strings with embedded newlines.
Sprite provides the function curry.symbol to find symbols by their
fully-qualified name:
>>> curry.symbol('Peano.S')
<curry constructor 'S'>
This function only searches loaded modules; it will not import anything.
The name splits at its first dot into a module and a name in it, so
curry.symbol('Data.List.nub') needs the package Data loaded, as the
import of Data.List above left it:
>>> curry.symbol('Data.List.nub')
<curry function 'nub'>
A Curry name that is not a Python identifier, or is a Python keyword, is
reached with getattr or with curry.symbol. Seven names of the
Prelude are keywords: not, and, or, break, return,
True and False.
>>> from curry.lib import Prelude
>>> getattr(Prelude, '++')
<curry function '++'>
Inspecting Types
Curry types and symbols reside in separate namespaces. To avoid collisions, types are not exposed as module attributes in Python.
Sprite provides curry.inspect.types to gain access to the types defined in
a module:
>>> from curry import inspect
>>> inspect.types(Peano)
{'Nat': <curry type 'Nat'>}
One may also use curry.type to look up a type by its fully-qualified name:
>>> curry.type('Peano.Nat')
<curry type 'Nat'>
A type object knows its constructors:
>>> Nat = curry.type('Peano.Nat')
>>> Nat.fullname
'Peano.Nat'
>>> Nat.constructors
(<curry constructor 'O'>, <curry constructor 'S'>)
curry.currytype() gives the Curry type a Python value converts to by
its Python type alone: curry.currytype(int) is Int and
curry.currytype(str) is Char. The typed builder below converts by
the expected type instead, so a str becomes a String where one is
expected.
Compiling Curry Code
Dynamic Compilation
Use curry.compile() to dynamically create Curry modules:
>>> Fib = curry.compile(
... '''
... fib :: Int -> Int
... fib n | n < 3 = 1
... | True = (fib (n-1)) + (fib (n-2))
...
... main :: Int
... main = fib 7
... '''
... , modulename='Fib'
... )
The above is equivalent to to placing the code into a file Fib.curry and
importing it. The module is added to curry.modules under its name.
Without a name, the module gets a name of its own that no later compile
reuses.
Note
The text reaches the front end as written. The layout rule of Curry accepts a block whose lines are indented alike, as shown above.
Each compile runs the Curry front end, which takes a fraction of a second
for a small module (0.6 s for Peano on the machine of this text, the
translation to ICurry included).
Writing Compiled Code to Disk
curry.save() writes the generated C++ of a module. To save the
compiled Fib module into a file Fib.cpp, say:
>>> curry.save(Fib, 'Fib.cpp', module_main=False)
module_main=False saves the module alone. Without it the file is a
program, and goal= must name the goal it evaluates; a call with neither
raises ValueError. The entry point of the C++ program is a stub today;
sprite-exec runs programs. The saved program of the reference backend
(The reference backend), a script in Python, retires at the removal gate
of issue #82.
curry.load() loads the shared object of a module, which
sprite-make --so writes beside the source, under
.curry/sprite-pakcs-3.4.1/ (see sprite-make). With the module
above in a file Fib.curry:
% install/bin/sprite-make --so Fib.curry
Then, in a new Python process (the note below says why):
>>> Fib = curry.load('.curry/sprite-pakcs-3.4.1/Fib.so')
Note
The runtime keeps one library per module name for the life of the
process. A load of Fib.so in a session where Fib is loaded
from another file is refused with the message “it is already loaded
from”, before the object is opened; load the object in a new process.
Under the default interpret:tiered of the C++ backend, an import of
Fib without a current object compiles it in the background, and
curry.load() of that object waits for the compile to end (the
log says so at the INFO level, and at the WARNING level once the wait
passes five seconds) and loads the object the compile wrote. The
object of a module that runs, or ran, interpreted in this process
without a library of its name loaded (its compile failed, or a reset
came before the compile ended) is refused as well. A new interpreter (Interpreter())
shares the process and its libraries, and starts from the flags of the
environment (Interpreter flags).
Building Expressions
The string to curry.compile is by default interpreted as a module
definition. To instead build an expression, set the mode to 'expr':
>>> fib3 = curry.compile(
... 'fib 3', mode='expr', exprtype='Int', imports=[Fib]
... )
>>> fib3
<fib <Int 3>>
Note the following:
exprtypeprovides the type annotation of this expression. See Important Notes.Adding
Fibto the import list makesFib.fibavailable.
exprtype is optional. Without it the front end infers the type of the
expression. A type with class constraints, such as Num a => a for
1 + 2, is defaulted as the REPL of PAKCS does (the table is in
Defaulting of class constraints below). A constraint the table cannot handle is a
CompileError with the sentence of the REPL. CompileError derives
from BaseException, not from Exception, so except Exception
does not catch it; name the class in the except clause:
>>> curry.compile('toEnum 65', mode='expr')
Traceback (most recent call last):
...
curry.exceptions.CompileError: cannot handle the overloaded expression 'toEnum 65' of type Enum a => a
Cannot handle arbitrary overloaded top-level expressions
add a type annotation (exprtype)
A trailing where x, y free declares free variables, as in the REPL. A
variable whose type is absent from the result type cannot show in a value,
so curry.eval reports its binding with each value:
>>> goal = curry.compile('xs ++ [3] =:= [1, 2, 3] where xs free', mode='expr')
>>> value = next(curry.eval(goal))
>>> print(value)
{xs=[1, 2]} True
Such a value is a Bindings
object: value.value is the value of the goal and value.bindings a
dict from the name of a variable to its binding, in the order of the
declaration. A variable whose type occurs in the result type is left in
the value, which is the answer:
>>> print(next(curry.eval(curry.compile('(x, 1) where x free', mode='expr'))))
(_a, 1)
The REPL of PAKCS prints the binding of every declared variable, {x=x} x
for x where x free, where Sprite prints _a. The clause must end the
text. With exprtype, or with a comment after free, it is compiled
as a local declaration. The variables are not lifted then, and no binding
is reported. So curry.compile('xs =:= [1] where xs free', mode='expr',
exprtype='Bool') evaluates to True alone.
Curry expressions can also be created directly in Python with
curry.expr(). It bypasses the Curry front end, so it is fast, and it
is typed: the expression is checked and converted before any node is built.
Typed Expressions below gives the rules.
Built-in Conversions
curry.expr converts numbers, strings, Booleans, lists, and tuples to Curry,
each by the type its position expects (see Typed Expressions). A few
examples:
>>> curry.expr(1)
<Int 1>
>>> curry.expr(1.5)
<Float 1.5>
>>> curry.expr('a')
<Char 'a'>
>>> curry.expr('ab')
<: <Char 'a'> <: <Char 'b'> <[]>>>
>>> curry.expr(True)
<True>
>>> curry.expr([1])
<: <Int 1> <[]>>
>>> curry.expr((1,2))
<(,) <Int 1> <Int 2>>
>>> curry.expr(())
<()>
The above values are shown in repr format. This format can be obtained by
applying the repr function. This format reflects the underlying graph
structure directly. Each node is rendered as an angle-bracket-enclosed
sequence comprising the symbol name followed by the successors. No special
formatting for lists, tuples, strings, or any other type is used.
Boxed values, such as <Int 1>, are easy to distinguish from unboxed ones.
To be boxed is synonymous with being stored in a node. The boxed integer
<Int 1> is a node with symbol Int and successor 1 (which is
unboxed). curry.unboxed marks the payload of such a node, and nothing
else. Anywhere but under Int, Char or Float a node is expected,
so the marker is an error at construction:
>>> from curry.lib import Prelude
>>> curry.expr([Prelude.Int, curry.unboxed(1)])
<Int 1>
>>> curry.expr(Prelude.id, curry.unboxed(1))
Traceback (most recent call last):
...
curry.typecheck.errors.ConversionError: curry.unboxed(1) stands at argument 1 of Prelude.id :: a -> a; the marker is the payload of an Int, Char or Float, as in [Prelude.Int, curry.unboxed(1)]
An alternative to repr format is str format. To obtain it,
apply the str function or just print the value:
>>> print(curry.expr(1))
1
>>> print(curry.expr('a'))
'a'
>>> print(curry.expr('hello'))
"hello"
>>> print(curry.expr([1]))
[1]
>>> print(curry.expr((1,2)))
(1, 2)
str format shows expressions in a more natural way, but discards
information about whether data is boxed. A string of several characters is
built in one call: the list of its characters, made natively.
Two nodes compare by their structure: == is True between two nodes that
hold the same expression, whatever their identity, and False between a node
and a value that is not a node. A node hashes by its identity, so two equal
nodes hash differently, and a dict or a set keyed by nodes keeps both; a
node is not meant as a dictionary key. A node has no order: <
between two nodes raises TypeError. Every node is true in a Boolean
context, the node False and a failure included, so convert a value
before a test. len is not defined on a node. v[i] is successor
i of the node, so v[0] of a list value is its head and v[1] its
tail. An index past the successors raises TypeError, and so does
list(v). A built-in value (Int, Char, Float) has no
successor. Its index 0 is the Python value it holds. For example:
>>> one, other = curry.expr(1), curry.expr(1)
>>> one == other, one == 1, one is other
(True, False, False)
>>> len({one, other})
2
>>> v = next(curry.eval(curry.expr([1, 2])))
>>> bool(next(curry.eval(False))), v[0], v[1]
(True, <Int 1>, <: <Int 2> <[]>>)
Symbolic Expressions
Curry symbols are converted to expressions:
>>> from curry.lib import Prelude
>>> curry.expr(Prelude.Nothing)
<Nothing>
To apply arguments, place them after the symbol:
>>> curry.expr(Prelude.Just, 5)
<Just <Int 5>>
>>> curry.expr(Fib.fib, 7)
<fib <Int 7>>
Partial applications are allowed:
>>> curry.expr(Prelude.Just)
<_PartApplic 1 Just>
A subexpression can be specified as a Python list whose first element is a symbol:
>>> print(curry.expr(Peano.S, [Peano.S, Peano.O]))
S (S O)
Note
Python lists build both Curry lists and nested Curry expressions. The rule is by the first element alone: a list whose first element is a symbol is an application of that symbol, and any other list is a Curry list. Lists are processed from the inside out. Consider:
>>> print(curry.expr([Peano.S, Peano.O]))
S O
This list specifies a subexpression because Peano.S is a symbol. On the
other hand:
>>> print(curry.expr([[Peano.O], [Peano.O]]))
[O, O]
The nested lists, [Peano.O], begin with a symbol and, therefore, specify
subexpressions. These are transformed first by applying curry.expr
recursively. By the time it is processed, the outermost list begins with an
expression rather than a symbol. So a Curry list whose first element is a
symbol cannot be spelled as a flat Python list: curry.expr([Peano.O])
is the value O, and curry.expr([Peano.O, Peano.O]) is an
ArityError. Spell such a list with one more level, [[Peano.O]],
with built nodes, [curry.expr(Peano.O)], or with
curry.cons(Peano.O, curry.nil).
Graph-Like Expressions
Named subexpressions can be created by passing keyword arguments. This is
necessary to create graph-like (as opposed to tree-like) expressions that
contain shared subexpressions and/or cycles. To reference a subexpression, use
curry.ref. The following creates an infinity in the Peano system:
>>> curry.expr(curry.ref('a'), a=[Peano.S, curry.ref('a')])
<S ...>
This is equivalent to the following Curry expression:
let a=(S a) in a
The ellipsis indicates a back reference. In general, repr will not render
a subexpression more than once. str format does not do this, so an attempt
to print the previous expression would never terminate.
Expression Modifiers
In addition to curry.ref, a few other helper functions and objects are
provided. To create a free variable, use curry.free. One marker is one
variable, also when it occurs several times or in several expressions. The
expression holds a call of Prelude.unknown, and the first rewrite step
turns it into the variable. curry.reset() starts a new interpreter
state, and the marker becomes a new variable there:
>>> xs = curry.free()
>>> print(curry.expr(xs))
unknown _inst#Prelude.Data#Prelude.Bool
>>> print(next(curry.eval(xs)))
_a
A binding made during one evaluation is private to that evaluation. The same marker in a later goal is the same variable node, unbound again:
>>> x = curry.free()
>>> eq = getattr(Prelude, '=:=')
>>> print(next(curry.eval(eq, x, 1)))
True
>>> print(next(curry.eval(x)))
_a
To create a non-deterministic choice, use curry.choice. The expression
holds a call of Prelude.?, and the first rewrite step turns it into a
choice with a fresh identifier, so two markers are two independent choices:
>>> print(curry.expr(curry.choice(1, 2)))
(?) 1 2
>>> sorted(curry.eval(curry.choice(1, 2), converter='topython'))
[1, 2]
>>> plus = curry.symbol('Prelude.+')
>>> sorted(curry.eval([plus, curry.choice(1, 2), curry.choice(10, 20)], converter='topython'))
[11, 12, 21, 22]
curry.raw_expr builds the raw Free and Choice nodes instead, with
the identifiers given to the markers. That form serves the tests of the
runtime.
Use curry.cons and curry.nil to create cons-style lists, and
curry.fail to place a failure:
>>> print(curry.expr(curry.cons(1, curry.nil)))
[1]
>>> print(curry.expr(curry.cons(1, 2, 3, curry.nil)))
[1, 2, 3]
>>> print(curry.expr(curry.fail))
failed
Typed Expressions
curry.expr() types an expression before it builds it. The type
scheme of each symbol comes from the FlatCurry interface of its module, the
signature shown above. The arguments are unified with the parameter
types. The class constraints that remain are defaulted with the table of
the PAKCS REPL (Defaulting of class constraints), and the class dictionaries are
supplied. So an overloaded function, and a class method, can be called
from Python:
>>> plus = curry.symbol('Prelude.+')
>>> print(curry.expr(plus, 1, 2))
apply (apply ((+) _inst#Prelude.Num#Prelude.Int) 1) 2
>>> next(curry.eval(plus, 1, 2, converter='topython'))
3
>>> next(curry.eval(plus, 1.5, 1, converter='topython'))
2.5
>>> next(curry.eval(Prelude.show, [Prelude.Just, 1], converter='topython'))
'Just 1'
curry.typeof() gives the type of an expression, of a symbol, or of
any argument curry.expr accepts: the inferred scheme before defaulting,
which is what :type prints in the REPL, or the type after
it:
>>> curry.typeof(curry.expr(plus, 1, 2))
'Num a => a'
>>> curry.typeof(curry.expr(plus, 1, 2), defaulted=True)
'Int'
>>> curry.typeof(plus)
'Num a => a -> a -> a'
>>> curry.typeof([1, 2.5])
'Fractional a => [a]'
>>> curry.typeof((1, 2.5))
'(Num a, Fractional b) => (a, b)'
The context is printed in the order of the front end: by the first type
variable of each constraint, then by the class. curry.typeof answers
for the nodes curry.expr returned. It answers for the values of
curry.eval by their content. For the goal of
curry.compile(mode='expr') it reports the type after defaulting,
Int for 1 + 2. For a goal whose binding is reported (a where x
free variable absent from the result type) it reports a bare type
variable.
The keyword exprtype states the type of the whole expression in Curry
syntax, and curry.typed states the type of one part. Both are
unified with the inferred type:
>>> curry.typeof(curry.expr(Prelude.Just, 5, exprtype='Maybe Float'))
'Maybe Float'
>>> next(curry.eval(Prelude.read, '5', exprtype='Int', converter='topython'))
5
>>> curry.typeof(curry.typed(1, 'Float'))
'Float'
Conversion rules. A Python value converts by the type its position expects. The Python type decides only where the expected type is a type variable.
boolconverts toTrueorFalse. Any other value underBoolis an error, socurry.expr(getattr(Prelude, 'not'), 1)is rejected.intconverts toInt; underFloatto aFloat; under another instance ofNumthroughfromInt. Under a type variable the variable gets the constraintNumand defaults toInt. Anintoutside the range ofIntis aCurryTypeError(Limits).floatconverts toFloat; under a type variable the variable gets the constraintFractional. AfloatunderIntis an error, as1.5 + (1 :: Int)is in Curry.strconverts to one string of any length underString([Char]):'a'underStringis"a", and''is the empty string. UnderCharastrof length one converts to the character. Under a bare type variable the Python type decides: astrof length one is aChar, and any otherstris aString.curry.typed('a', 'String')makes the one-character string. The decision waits for the rest of the expression. A part that fixes the variable to a list makes the one-character string a string too:curry.typeof(['c', 'ab'])is'[[Char]]', and so iscurry.typeof(['ab', 'c']).fmap ord 'a'is[97]: thef aoffmapis a type that only a list can fill.
bytesconvert asstr.listconverts to a Curry list. Every element takes the element type, and all elements unify before defaulting, so[1, 2.5]is[Float], as in Curry. A list is built in a loop, so the builder puts no limit on its length.tupleconverts to a Curry tuple;()is the unit. Curry has no 1-tuple, so a 1-tuple is an error. The Prelude declares the tuple types up to 15 components; a tuple of 16 or more types and then fails with aSymbolLookupErrorthat names the missing constructor.An iterator converts to a lazy Curry list. The items are read when the list is demanded. Of two expressions built on one iterator, the one evaluated first takes the items, and the other gives
[]. An exhausted iterator converts to[]. Only an iterator converts: arangeis aCurryTypeError; passiter(range(n)). The element type is fixed when the expression is built, by the context, by the other arguments or by the defaulting table, never by an item. So a class constraint that only the items could resolve, as inmap showover an iterator of numbers, is an error;curry.typed(iterator, '[Int]')states the element type. An item that does not convert raises anEvaluationErrorwhen it is demanded, which ends the evaluation, inside?and inside a set function alike:>>> list(curry.eval(getattr(Prelude, '++'), [1], iter([2, 'a']), converter='topython')) Traceback (most recent call last): ... curry.exceptions.EvaluationError: cannot convert item 'a' of the iterator at argument 2 of Prelude.++ :: [a] -> [a] -> [a] to Int
Noneis an error that names the expected type. UnderMaybe tthe message adds the linePrelude.Nothing is the empty value of Maybe.A Curry node is typed by its content; see below.
curry.freeis a free variable; see below.curry.unboxedis the unboxed payload of anInt,CharorFloat, as in[Prelude.Int, curry.unboxed(3)]. The payload must fit the primitive: a PythonintunderInt, astrof length one underChar, afloatunderFloat; another value is aConversionErrorthat names the primitive. Anywhere else the marker is aConversionError; as an item of an iterator it is anEvaluationErrorwhen the list is demanded.
Values of earlier evaluations. A Curry node that fills a parameter is
typed by a walk of its content, to the leaves, not by its root alone. A
value of curry.eval() is such a node and carries no type of its own.
This walk is what keeps a list of floats away from integer arithmetic:
>>> v = next(curry.eval(curry.expr([1.5, 2.5])))
>>> curry.typeof(v)
'[Float]'
>>> DL = curry.import_('Data.List')
>>> next(curry.eval(DL.sum, v, converter='topython'))
4.0
An evaluation rewrites the goal node in place. Afterwards the node holds
the result, or it forwards to the result. repr shows a forward node as
<_Fwd <Int 2>> in the example below, or as <_FwdSz <O> 24> for a
constructor value. curry.topython() returns a forward node
unconverted, while curry.eval and curry.typeof accept it:
>>> e = curry.expr(Fib.fib, 3)
>>> next(curry.eval(e, converter='topython'))
2
>>> e
<_Fwd <Int 2>>
>>> curry.topython(e)
<_Fwd <Int 2>>
>>> curry.typeof(e)
'Int'
A partial application is typed by the scheme of its head and the arguments
it holds, so map not from an earlier evaluation has the type [Bool]
-> [Bool] and apply refuses a number for it. A shared subgraph is
visited once, but a node without successors, such as [], is typed at
each position it holds: the C++ backend has one [] node for every
empty list, so the value of ([1], 'ab') holds it at two types, and the
sharing says nothing about the type. The walk stops at 100000 positions
with an error (a shared [] counts once per position, a shared cell
once). It also refuses a value whose type would
print with more than that many nodes; a value that shares one node between
the components of a pair at every level has such a type. State the type
of a larger value with curry.typed(node, '[Float]'), which skips the
walk:
>>> big = next(curry.eval(curry.expr(list(range(100001)))))
>>> curry.typeof(big)
Traceback (most recent call last):
...
curry.typecheck.errors.ValueTooLargeError: the value at the expression has more than 100000 nodes; pass curry.typed(node, 'T')
>>> curry.typeof(curry.typed(big, '[Int]'))
'[Int]'
The annotation is trusted: curry.typeof(curry.typed(big, 'Bool')) is
'Bool', and an expression built on a wrong annotation is ill-typed, as
one of curry.raw_expr() can be.
A node alone, or at the root of the expression, passes through untouched.
The scope of inference. One call of curry.expr is one inference
scope. The class constraints of the call are defaulted when it returns,
and a later call does not reopen them:
>>> incr = curry.expr(plus, 1)
>>> curry.typeof(incr, defaulted=True)
'Int -> Int'
>>> curry.expr(Prelude.apply, incr, 1.5)
Traceback (most recent call last):
...
curry.typecheck.errors.ConversionError: cannot convert 1.5 to Int at argument 2 of Prelude.apply :: (a -> b) -> a -> b
To let the outer context decide, build the expression in one call, or
build a description with curry.describe(), which is typed with the
expression that receives it:
>>> next(curry.eval(Prelude.apply, [plus, 1], 1.5, converter='topython'))
2.5
>>> d = curry.describe(plus, 1)
>>> print(d)
(+) 1
>>> d.typeof()
'Num a => a -> a'
>>> next(curry.eval(Prelude.apply, d, 1.5, converter='topython'))
2.5
A description keeps the keyword anchors of its arguments and the keyword
exprtype; every use of it is typed afresh, so one description serves
several contexts. A symbol without a type scheme is built untyped when
it stands alone, as the goal of a saved module is. Such a symbol comes
from a module without a FlatCurry interface, or it is a built-in of
Sprite’s own Prelude such as Prelude.IO. curry.typeof()
refuses it with an InterfaceError that names raw_expr, and so does
an application of it.
Free variables. A curry.free marker is one variable, as
described above. Its type comes from the context. The marker carries no
constraint of its own, so curry.eval(Prelude.id, x) gives _a with
the type a. A consumer that needs Data, such as =:=, adds the
constraint, and a lone Data constraint defaults to Bool, as in
PAKCS:
>>> x, y = curry.free(), curry.free()
>>> eq = getattr(Prelude, '=:=')
>>> curry.typeof(curry.expr(eq, x, y))
'Data a => Bool'
>>> print(next(curry.eval(eq, x, y)))
True
curry.free(exprtype='[Int]') fixes the type of a variable. A free
variable at a function type is an error at construction, because a free
variable of Curry must have a Data type.
Arithmetic on a free numeric variable does not narrow the variable. A
free variable of a built-in type that reaches a case suspends the
evaluation (issue #37), and the typed builder does not
change that: x + 1 =:= 3 suspends, while x =:= 3 binds x, and a
conjunction in which another constraint binds x succeeds in either
order:
>>> x = curry.free()
>>> next(curry.eval(eq, [plus, x, 1], 3))
Traceback (most recent call last):
...
curry.exceptions.EvaluationSuspended: Evaluation Suspended!
>>> y = curry.free()
>>> conj = getattr(Prelude, '&')
>>> print(next(curry.eval(conj, [eq, y, 3], [eq, [plus, y, 1], 4])))
True
The binding optimization of the toolchain turns a required Boolean
equality of a Curry program into the constraint =:=, as PAKCS does,
so a guard x == 3 in a program binds x; see
Curry to ICurry.
Errors. A typing failure raises CurryTypeError at construction; nothing is evaluated.
CurryTypeError is a TypeError, so except Exception catches it,
unlike CompileError and EvaluationError.
The message names the symbol with its scheme, the argument, and the types.
This is the catalogue, with one example of each error
(curry.typecheck.errors). Every class below is a subclass of
CurryTypeError. The errors of the engine are subclasses of
TypeCheckError, which carries the facts of the message as the
attributes where, symbol, expected, actual and value.
MismatchErrorTwo arguments disagree, or an argument does not fit its parameter.
>>> curry.expr(plus, 1, 'a') Traceback (most recent call last): ... curry.typecheck.errors.MismatchError: type mismatch in argument 2 of Prelude.+ :: Num a => a -> a -> a argument 1 fixed a := Int (from 1) argument 2 has type Char (from 'a')
ConversionErrorA Python value under a type it cannot take.
Noneis such a value under every type, and so is a 1-tuple.>>> curry.expr(getattr(Prelude, 'not'), 1) Traceback (most recent call last): ... curry.typecheck.errors.ConversionError: cannot convert 1 to Bool at argument 1 of Prelude.not :: Bool -> Bool >>> curry.expr(curry.symbol('Prelude.maybe'), 0, Prelude.id, None) Traceback (most recent call last): ... curry.typecheck.errors.ConversionError: cannot convert None at argument 3 of Prelude.maybe :: a -> (b -> a) -> Maybe b -> a; expected Maybe a Prelude.Nothing is the empty value of Maybe
AmbiguousTypeErrorA class constraint on a type variable that is absent from the result type, which nothing fixes and the table cannot default; the front end rejects the same text.
>>> curry.expr(Prelude.show, []) Traceback (most recent call last): ... curry.typecheck.errors.AmbiguousTypeError: ambiguous type in show [] :: Show a => [Char] Ambiguous type variable a in type Show a => [Char] Cannot handle arbitrary overloaded top-level expressions defaults exist for Num and Integral (Int), Fractional and Floating (Float), Monad (IO) and Data (Bool); pass exprtype, for example exprtype='Int'
DefaultingError(curry.typecheck.defaulting)A class constraint in the result type that the table cannot default. The message has the sentence of the PAKCS REPL;
exprtyperesolves it. The same failure is aCompileErrorfor a text goal ofcurry.compile.>>> curry.expr(Prelude.read, '5') Traceback (most recent call last): ... curry.typecheck.defaulting.DefaultingError: cannot handle the overloaded expression 'read "5"' of type Read a => a Cannot handle arbitrary overloaded top-level expressions add a type annotation (exprtype)
NoInstanceErrorA constraint on a type that has no instance of the class.
>>> curry.expr(Prelude.show, Prelude.id) Traceback (most recent call last): ... curry.typecheck.errors.NoInstanceError: no instance for Show (a -> a) in argument 1 of Prelude.show :: Show a => a -> [Char]
FreeFunctionErrorA free variable at a function type.
>>> curry.expr(Prelude.apply, curry.free(), 1) Traceback (most recent call last): ... curry.typecheck.errors.FreeFunctionError: free variable at argument 1 of Prelude.apply :: (a -> b) -> a -> b would have the function type a -> b Curry free variables must have a Data type
ArityErrorA constructor applied to more arguments than it takes. Also a function whose result type is a data type, applied to more arguments than its type has arrows:
inc :: Int -> Intwith two arguments. A function whose result type is a function type or a type variable takes the extra arguments throughPrelude.apply. Fewer arguments give a partial application.>>> curry.expr(Prelude.Just, 1, 2) Traceback (most recent call last): ... curry.typecheck.errors.ArityError: Prelude.Just :: a -> Maybe a takes 1 argument, 2 given
ValueTooLargeErrorThe walk that types a Curry value stopped at its cap of 100000 positions; the example is above.
ExprTypeSyntaxError,ContextInExprTypeError,UnknownTypeConstructorError,KindError,ExprTypeMismatchErrorThe errors of an
exprtypestring or acurry.typedannotation, all subclasses ofExprTypeError:ExprTypeSyntaxError: a text that does not parse.ContextInExprTypeError: a context in the text; the context is inferred.UnknownTypeConstructorError: a type constructor that no loaded interface declares.KindError: a type constructor applied to the wrong number of arguments.ExprTypeMismatchError: a type that does not unify with the inferred one.
>>> curry.expr(1, exprtype='Maybe (') Traceback (most recent call last): ... curry.typecheck.errors.ExprTypeSyntaxError: cannot parse exprtype 'Maybe (': expected a type, found end of text at column 8 >>> curry.expr(1, exprtype='Num a => a') Traceback (most recent call last): ... curry.typecheck.errors.ContextInExprTypeError: exprtype takes a type without a context; the context is inferred (exprtype 'Num a => a') >>> curry.expr(1, exprtype='Foo') Traceback (most recent call last): ... curry.typecheck.errors.UnknownTypeConstructorError: unknown type constructor Foo; searched the interfaces of Prelude, Peano, Data.Maybe, Data.List, Fib; curry.compile(..., exprtype=...) accepts any Curry type >>> curry.expr(1, exprtype='Maybe Int Int') Traceback (most recent call last): ... curry.typecheck.errors.KindError: Maybe takes 1 type argument, 2 given in exprtype 'Maybe Int Int' >>> curry.expr(Prelude.Just, 1, exprtype='Int') Traceback (most recent call last): ... curry.typecheck.errors.ExprTypeMismatchError: exprtype Int does not match the inferred type Maybe a
InterfaceError(curry.typecheck.sigtable)A symbol without a type scheme where one is needed.
>>> curry.typeof(Prelude.IO) Traceback (most recent call last): ... curry.typecheck.sigtable.InterfaceError: no type for Prelude.IO: the FlatCurry interface .../Prelude.fint has no entry 'IO'; use raw_expr
The untyped builder. curry.raw_expr() builds an expression
without types. It converts every Python value by its Python type, checks
only the arity of a symbol, supplies no dictionary, and builds the raw
Free and Choice nodes with the identifiers of the markers. It
serves the runtime and its tests.
Warning
curry.raw_expr() can produce an ill-typed expression. Evaluating
an ill-typed expression results in undefined behavior: raw_expr(plus,
1, 2) builds + without its Num dictionary, and the evaluation
of such a node can end the process.
The interpreter flag typed_expr (True by default) turns the typing
of curry.expr() off for a whole interpreter; see
Interpreter flags.
Evaluating Expressions
To evaluate an expression pass it to curry.eval.
>>> print(next(curry.eval(fib3)))
2
>>> print(next(curry.eval(Fib.main)))
13
curry.eval returns a generator that yields one value with each invocation
of next. The goal is evaluated lazily, so next performs only the
computational steps it must to compute the next value.
The arguments of curry.eval are those of curry.expr: a symbol with
its arguments, a Python value, a node, or a description, with the keyword
exprtype. So curry.eval(plus, 1, 2) builds the expression and
evaluates it. A str is a value there, as in curry.expr:
curry.eval('1 + 2') yields the string "1 + 2". A text goal goes
through curry.compile(mode='expr').
A function of a module without a type signature keeps its class
constraints, which the front end turns into leading dictionary parameters.
curry.eval supplies the dictionaries after the defaulting of
Defaulting of class constraints, so main = Just 5 evaluates to Just 5 and
not to a partial application. A call with arguments,
curry.eval(M.addOne, 1), is typed like any expression of
curry.expr().
By default, no conversions are performed. That means the 13 returned above
is a Curry integer rather than a Python integer. We can see this by looking at
the repr format:
>>> next(curry.eval(Fib.main))
<Int 13>
An evaluation that fails in Curry raises an
EvaluationError from next.
EvaluationSuspended is raised when an alternative suspends on a free
variable, after the values of the alternatives that did not suspend. An
error of an IO action, a user error, an i/o error or a
non-deterministic action, is an EvaluationError whose message names
it, as error is.
EvaluationError derives from BaseException, not from
Exception, as CompileError does, so except Exception does not
catch it; name the class in the except clause. An expression with no
value ends the generator without a value.
Defaulting of class constraints
A goal without a type annotation keeps the class constraints the front end
inferred. curry.eval, curry.compile(mode='expr'), the REPL,
sprite-exec -g and curry.save default them with the table of the
PAKCS REPL (defaultQualType of its c2p.pl), which
curry.typecheck.defaulting implements:
NumandIntegraldefault toInt.FractionalandFloatingdefault toFloat.MonadandMonadFaildefault toIO.Data,Eq,Ord,ReadandShoware dropped on a variable defaulted toIntorFloat;Enumis dropped on a variable defaulted toInt;Monadis dropped onIO.A variable that carries
Dataalone defaults toBool.Anything else is an error with the sentence of the REPL,
Cannot handle arbitrary overloaded top-level expressions:toEnum 65(Enum a) andread "5"(Read a) are such goals, and a type annotation resolves them.
A type variable without a constraint stays polymorphic. A goal of type
IO t runs during the evaluation, and the value is the result of the
action. tests/func_goal_defaulting.py pins the table against the
REPL of PAKCS.
Multi-Valued Computations
Non-deterministic computations are in general multi-valued. To partially evaluate such an expression, simply stop taking values and discard the generator. For example, to print the Fibonacci numbers less than 30 one could say:
>>> fibs = curry.compile(
... 'anyOf (map fib [1..])', mode='expr', exprtype='Int', imports=[Fib]
... )
>>> for value in curry.eval(fibs, converter='topython'):
... if value < 30:
... print(value)
... else:
... break
1
1
2
3
5
8
13
21
An error or a suspension in one alternative ends the generator. The
values of the alternatives before it are produced. The error is raised
from next in its turn, and no value follows it. So 1 ? error "boom"
? 2 yields 1 and then raises. A generator that is not exhausted
keeps its place: next continues it, also after other evaluations.
The order of the values of a non-deterministic goal depends on the
schedule of the backend. On the C++ backend in time mode, the default,
the order may differ from one run to the next; step mode pins it (the
flag rotation; see Interpreter flags).
Conversions to Python
curry.expr() provides conversions from Python to Curry. The reverse can
be performed with curry.topython(). This function recursively converts a
Curry expression to Python.
Conversions to the following Python types are performed: bool, float,
int, list, str, tuple.
The keyword converter of curry.eval() converts each value as it
is produced: 'topython' applies curry.topython(), None leaves
the Curry value, and a callable f(interp, value) converts as it
pleases. The default, 'default', is the interpreter flag
defaultconverter, None unless set:
>>> curry.flags['defaultconverter'] = 'topython'
>>> next(curry.eval([1, 2]))
[1, 2]
>>> next(curry.eval([1, 2], converter=None))
<: <Int 1> <: <Int 2> <[]>>>
>>> curry.flags['defaultconverter'] = None
A Curry Char is a Unicode code point. A Python str converts to a
String where a String is expected and a str of length one to a
Char where a Char is expected (see Typed Expressions); a
String converts back to a str. Both directions keep every code
point. The files that readFile, writeFile,
and appendFile touch hold UTF-8, and so do the standard streams of
putChar and getChar.
curry.topython() takes an optional exprtype, the static type of
the value in Curry syntax, and threads it through lists and tuples, so an
empty String converts to ''. curry.eval() passes the type of
the goal it built to the converter. Without a type, an empty list converts
to [] and a list of characters to a str:
>>> empty = next(curry.eval(curry.typed('', 'String')))
>>> curry.topython(empty)
[]
>>> curry.topython(empty, exprtype='String')
''
>>> next(curry.eval(curry.typed('', 'String'), converter='topython'))
''
curry.topython() prunes the recursion wherever it encounters a
subexpression it cannot convert. The reason for this potentially
counter-intuitive behavior is made clear by the following example:
>>> just5 = curry.expr(Prelude.Just, 5)
>>> curry.topython(just5)
<Just <Int 5>>
One might expect the boxed integer to be converted to a Python integer, but that would lead to an ill-typed expression. Sprite leaves it to the user to avoid this or convert such values another way.
A value with a free variable prints the variable as _a, _b and so
on; curry.show_value() gives that text for a converted value too.
A Float prints as PAKCS prints it: the shortest digits that read back,
1.0e+16 for a large value, and 1.0 for an integral one.
Limits
The walk that types a Curry value stops at 100000 positions, a shared constant such as
[]counted once per position (ValueTooLargeErrorabove);curry.typed(node, 'T')states the type instead.An evaluation is bounded by the C stack it may use, the flag
stack_limit; a deeper alternative is dropped with its error reported after the others (the entry of the flag).Arithmetic on a free variable of a built-in type suspends (issue #37); the binding optimization and
=:=are the way to bind such a variable.An expression built by
curry.raw_expr()is not checked.A tuple has at most 15 components, the largest tuple type the Prelude declares; a longer tuple is a
SymbolLookupErrorat construction.An iterator is read when the list is demanded, so an exhausted iterator converts to the empty list. A
rangeis aCurryTypeError; passiter(range(n)).Intis a signed 64-bit integer, from -9223372036854775808 to 9223372036854775807. A Pythonintoutside the range is aCurryTypeErrorwhere it enters an expression; so is anIntliteral outside the range in a Curry source, when its module is loaded or its function is first evaluated. The minimum is not a literal:-9223372036854775808negates the literal9223372036854775808, which is outside the range; write-9223372036854775807 - 1. An addition, a subtraction, a multiplication, or a negation whose result is outside the range raisesEvaluationError, as dodivandquotof the minimum by -1, andtruncate,round,ceilingandfloorof aFloatoutside the range, of an infinity, or of NaN. The message names the operation and its operands:integer overflow: 9223372036854775807 + 1,integer overflow: truncate 1.0e+30(ceilingandfloorconvert throughtruncateand name it);negateis0 - xin the Prelude and names that subtraction. An enumeration that ends at the maximum,[9223372036854775806 .. 9223372036854775807], computes the successor of its last element and raises too.div,mod,quotandremby zero raiseEvaluationError:division by zero: div 1 0. The division of aFloatby zero is not an error: it gives an infinity, or NaN for0.0 / 0.0.readof a numeral outside the range ofIntfails:reads "9223372036854775808" :: [(Int, String)]andreadof it have no value.catchtakes an integer overflow or a division by zero as it takeserror: the handler receives anIOErrorwith the message. The reference backend of the developer notes passes these errors throughcatch.