IvoryScript user guide

1 Introduction

This user guide provides a comprehensive overview of IvoryScript, a functional programming language designed for dynamic data types, uniform data persistence and explicit control over evaluation.

It explains key concepts and practical usage, highlighting distinctive features compared to more conventional languages. Whilst this guide focuses on practical aspects of the language, a formal specification of the syntax and semantics is provided by the reference manual.

Many of the syntactic constructs ultimately transform into a much simpler core language. One particular feature of which is that all evaluation is explicit, so the meaning of reduction and evaluation operators and coercion for different syntactic terms is explained fully to enable a comprehensive understanding of the meaning of expression values.

Although the language is generally purely functional, mutable heap variables are supported for changes of application state.

Programming in the language involves scripts written in its grammar, which a compiler translates into either bytecode for an interpreter or C++ source code for further compilation by external tools.

As it continues to evolve, this guide will be updated to reflect new features; such as derived classes etc.

2 Lexical overview

Scripts consist of ASCII characters that are are subdivided into tokens based on the following rules.

2.1 Comments

Two types of comments are supported:

  • Single-line comments start with -- and continue to the end of the line.

  • Nested comments start with {- and end with -}. Comment symbols within string codes do not introduce comments.

2.2 Whitespace and tokens

Whitespace characters (spaces, tabs, newlines) serve as separators between tokens like identifiers, keywords, literals and operators. Consecutive whitespace characters are treated as a single separator.

2.3 Names

A name consists of letters, digits and underscores. It must start with a letter, and names are used in different contexts dependent on the case of the first letter. e.g. x, Ptr and temp_123 etc.

2.4 Keywords

Lowercase words like

class, inline, let, module and type etc.

are reserved keywords with special meaning and so unavailable for use as general identifiers. For a full list see.

2.5 Special symbols

Special symbols, such as ;, (), [], :=, ->, ::, !, ^ and #, have specific meaning as described in the language syntax. For a full list see.

2.6 Numeric literals

Numeric literals can be either integer or floating-point:

  • Integer literals consist of digits (e.g. 6144). Hexadecimal form if there is a 0x or 0X prefix (e.g. 0x23FF).

  • Floating-point literals consist of digits, a decimal point and may include an exponent (e.g. 3.14e2).

2.7 Character and string literals

Character literals are single characters enclosed in single quotes, and they support 'C' style escape sequences (e.g. '\n' for newline). For a full list see.

String literals are sequences of characters enclosed in double quotes and also support the same escape sequences (e.g. 'Hello, world\n').

3 Script forms: Order and Module

IvoryScript supports two primary script forms: Order and Module.

3.1 Order script

An Order script is typically associated with an application, such as a session in the console. It comprises a sequence of expressions that are executed in order. Each step in a sequence is typically either an action or a value to be shown.

An example of an Order script:

tan (radians 45);
sqrt 2;
let {
   x :: Int = 10;
   y :: Int = x * 2;
   z :: Int = y + 5
} in
   z
      

For the last, the variables x, y and z are assigned sequentially, and then the value of z is shown.

3.2 Module script

Module scripts are designed to define reusable names associated with types, classes, functions etc. Modules serve as libraries or packages that can be imported and used elsewhere in an application. Although the primary use is for declarations and definitions that are initialised in definition order, initialisation can cause side effects - although this is not something to be generally recommended.

An example of a Module script:

module MathOperations where {

square :: Int -> Int;
square x = x * x;

cube :: Int -> Int;
cube x = x * x * x;

sideEffect :: Int = {show "Module initialised"; 42}

}
      

This module defines reusable functions, but it also includes a side effect that is executed when the module is initialised.

4 Namespaces

Names may occur in four distinct namespaces.

  • Type namespace

  • Class namespace

  • Module namespace

  • Value namespace

4.1 Naming conventions

In IvoryScript, Type, Class, and Module names must begin with a capital letter. In contrast, names in the Value namespace can begin with either an uppercase or lowercase letter.

A name can appear in multiple namespaces. For example, Point can be a type constructor in the Type namespace and a data constructor in the Value namespace:

type Point = Point Double Double  -- 'Point' as both type constructor and data constructor
let p :: Point = Point 1.0 1.0    -- 'Point' constructs a value of type Point
      

In this case, Point is a type constructor when defining the type and a data constructor when creating a value.

4.2 Type namespace

The Type namespace holds type constructor names, including built-in types (Int, Double) and user-defined types.

type Point = Point Double Double
      

Here, Point is introduced as a type constructor in the Type namespace.

4.3 Class namespace

The Class namespace contains type classes, which define sets of functions or operations that can be implemented for various types.

class Point a where
  move :: a -> a
      

In this case, Point is a type class in the Class namespace that defines a move function.

4.4 Module namespace

The Module namespace holds module names, which group related functions, types, and values. Modules allow for code organization and reusability.

module Point where
  origin = Point 0 0
      

Here, Point is the name of a module in the Module namespace.

4.5 Value namespace

The Value namespace holds variables, functions, constants, and data constructors—names representing values or operations on them.

let p :: Point = Point 1.0 1.0
      

In this example, Point is a data constructor in the Value namespace, used to create a value of type Point.

4.6 Summary of the four namespaces

Namespace Contains Example
Type Types, type constructors Int, Point
Class Type classes Eq, Point
Module Module names MathOps, Point
Value Variables, functions, constants, data constructors x, Point

5 Data types and signatures

5.1 Introduction

IvoryScript’s type system is designed to maintain clarity and safety, ensuring that values are used consistently with their declared types. This chapter introduces the core types in IvoryScript, including primitive types, type signatures, function types, and the essential Ptr a. Types such as List and Bool are not part of the core language but are expected to be present in the prelude or list modules.

5.2 Primitive Types

IvoryScript supports several primitive types that serve as the foundation for more complex structures. These include:

  • Int: Represents integer numbers.

                      
    let x = 42  -- x is inferred as Exp Int
    let y = !x  -- y is of type Int
            
                   
  • Double: Represents floating-point numbers.

                      
    let z = 3.14  -- z is inferred as Exp Double
    let w = !z    -- w is of type Double
            
                   
  • Char: Represents a single character.

                      
    let letter = 'a'  -- letter is inferred as Exp Char
    let strictLetter = !letter  -- strictLetter is of type Char
            
                   
  • String: Represents a sequence of characters (a list of Char).

                      
    let greeting = "Hello, World!"  -- greeting is inferred as Exp String
    let strictGreeting = !greeting  -- strictGreeting is of type String
            
                   

Primitive types are inferred as lazy expressions (Exp a) by default. When a value is forced with ! or #, it changes to its strict type a.

5.3 Name and Type

IvoryScript introduces two critical built-in types:

  • Name: Typically, name values result from a literal, such as #Mary, or from the special binding syntax, e.g., Mary:"Had a little lamb". For the latter, a pattern Bind name _ matches the name.

  • Type: Type values can be created from a literal type signature, such as let typeOfVar = #::Int, and are also the return value of the typeOf function, which provides reflection capabilities.

These types are essential for working with more dynamic aspects of IvoryScript, allowing you to reference and manipulate names and types directly.

5.4 Type Signatures

Type signatures in IvoryScript explicitly declare the types of values and functions. These signatures ensure correctness and make the function's input and output types clear.

The general form of a type signature is:

            
name :: Type
    
         

For example, a function that adds two integers might have the following type signature:

            
add :: Int -> Int -> Int
    
         

This indicates that add takes two arguments of type Int and returns an Int. For lazy values, the signature might be:

            
add :: Exp Int -> Exp Int -> Exp Int
    
         

5.5 Function Type Signatures

Function types are a core part of IvoryScript’s type system. They define how functions take arguments and return results.

The arrow (->) separates argument types from the return type:

            
multiply :: Int -> Int -> Int
    
         

This shows that multiply takes two Int values and returns an Int. For functions that operate on lazy values:

            
multiply :: Exp Int -> Exp Int -> Exp Int
    
         

Function types can also be higher-order, where a function returns another function:

            
compose :: (b -> c) -> (a -> b) -> (a -> c)
    
         

Here, compose is a function that takes two functions as arguments and returns a new function.

5.6 Lazy and Strict Types

IvoryScript distinguishes between lazy and strict types:

  • Exp a: Represents a lazy expression of type a. Lazy expressions are not immediately evaluated and only reduced when explicitly forced.

                      
    let x = 42  -- x is of type Exp Int
    let y = #42 -- y is of type Int
            
                   
  • a: Represents a strict, fully evaluated type.

Values are typically inferred as Exp a unless explicitly forced to be strict using the ! or # operators. This allows IvoryScript to manage evaluation efficiently.

5.7 Tuples

Tuples group multiple values of potentially different types into a single structure. They are enclosed in parentheses, with values separated by commas.

            
let point = (3, 4)  -- point is of type Exp (Int, Int)
    
         

Strict tuples can be defined as well:

            
let strictPoint = !(point)  -- strictPoint is of type (Int, Int)
    
         

Type signatures for functions that use tuples follow this pattern:

            
first :: Exp (a, b) -> Exp a
    
         

This signature shows that the first function takes a lazy tuple and returns the first element lazily.

5.8 Ptr a

The Ptr a type is an essential primitive type in IvoryScript. It represents a pointer to a value of type a and is primarily used for referencing memory or interacting with lower-level aspects of the runtime system. The use of Ptr a is primarily for operations that involve state changes or for efficient memory access in embedded or performance-critical applications.

  • Ptr a: Represents a pointer to a value of type a.

                      
    let ptr = Ptr someValue  -- ptr is of type Exp (Ptr a)
            
                   

The primary intention of Ptr a is for managing state changes or mutable references within an application. Although Ptr a can be used imperatively, doing so is generally less efficient than a pure, tail-recursive functional approach.

Pointers allow interaction with application state in a controlled manner:

            
let p = Ptr 10  -- p is of type Ptr Int
    
         

While Ptr a provides a way to handle mutable state, its use should be limited to genuine scenarios requiring changes to application state. Pure functional recursion often achieves similar results in a more efficient and declarative manner.

5.9 Conclusion

IvoryScript’s type system introduces essential types such as Name, Type, and Ptr a, alongside primitive types like Int and Char. The distinction between lazy (Exp a) and strict (a) types ensures that evaluations are performed only when necessary, allowing for efficient management of resources. Function types further provide a foundation for defining behavior, whether working with lazy or strict values.

6 Values, reduction and evaluation

6.1 Strict and lazy values

Everything in IvoryScript is treated as a value, including expressions that require further reduction or evaluation. Values can be strict or lazy, depending on their declaration.

A strict value is fully evaluated when used, while a lazy value represents an expression that can undergo further reduction when needed.

There is nothing special about functions returning lazy values. Coercion often transforms lazy values into their appropriate forms without requiring explicit reduction or evaluation.

6.2 Coercion and function types

The coercion mechanism automatically adjusts values to fit the context in which they are used, reducing the need for explicit use of (!) or !. However, because of this flexibility, it is essential to declare a function’s type, particularly for recursive functions.

The example below demonstrates how IvoryScript represents lazy and nested expressions:

            
typeOf (take 100 (!primes))
-- Output: Exp (Exp [Int])
    
         

In this case, Exp (Exp [Int]) indicates that the result of take 100 operates on a value that itself still requires further evaluation, even after a single reduction step. The outer Exp refers to the need for additional reductions to retrieve the result, while the inner Exp [Int] signifies that the list of integers has yet to be fully evaluated. Thus, Exp represents the delayed or pending computation that will eventually yield the final value.

This is quite different from many other languages in common use, where lazy values or deferred evaluations are less explicit or hidden from the developer. In IvoryScript, this flexibility requires managing types carefully, especially with recursive functions or lazy expressions that may need multiple steps of reduction before becoming usable values. Declaring types such as Exp explicitly allows control over how and when evaluation occurs, providing the benefit of lazy computation while maintaining clarity about the process.

7 Expression forms

7.1 Introduction

This chapter provides an overview of the primary expression forms in IvoryScript. The goal is to offer a practical understanding of how different expressions are used, balancing between detailed reference and introductory examples.

7.2 Literal expressions

Literal expressions represent direct values in IvoryScript. They include numbers, strings, booleans, and more.

Example:

            
42         -- Integer literal
"hello"    -- String literal
True       -- Boolean literal
    
         

Literal expressions are straightforward and are used to represent fixed values that do not require further reduction.

7.3 Variable expressions

Variable expressions refer to names that have been previously defined in a scope. They are used to access values stored in variables.

Example:

            
let {
    x = 10;
    y = x * 2
} in
    y   -- Variable 'y' refers to the result of 'x * 2'
    
         

Variables in IvoryScript are immutable, meaning once a value is assigned, it cannot be changed. Variable expressions rely on earlier bindings within their scope.

7.4 Function application

Function application is the most common way of invoking functions in IvoryScript. It consists of calling a function with one or more arguments.

Example:

            
let {
    square x = x * x;
    result = square 5
} in
    result  -- Applies the function 'square' to the argument 5
    
         

Function application is reduced by passing the argument(s) to the function and reducing the function body with these arguments.

7.5 Lambda expressions

Lambda expressions represent anonymous functions in IvoryScript. They are defined using the syntax \x -> expression where x is the argument.

Example:

            
let {
    add = \x y -> x + y;
    result = add 3 7
} in
    result  -- Applies the lambda function to arguments 3 and 7
    
         

Lambda expressions are useful for defining functions inline, especially when the function is only needed in a limited scope.

7.6 Let expressions

The let expression is used to introduce local bindings. Variables defined in a let block are only visible within that block.

Example:

            
let {
    x = 5;
    y = x + 3
} in
    y   -- Returns 8, as 'x' and 'y' are local to the let block
    
         

Let expressions allow local calculations and function definitions that do not affect the global state of the program.

7.7 If/Then/Else expressions

Conditional expressions are written using the if/then/else structure. IvoryScript evaluates the condition and returns the corresponding branch.

Example:

            
let {
    result = if x > 0 then "positive" else "non-positive"
} in
    result
    
         

The condition must reduce to a boolean, and the result depends on whether the condition holds true.

7.8 Case expressions

Case expressions allow for pattern matching against different values. They are useful for deconstructing data types and handling multiple cases explicitly.

Example:

            
let {
    describeList lst = case lst of {
        [] -> "Empty list";
        _ :+ _ -> "Non-empty list"
    }
} in
    describeList [1, 2, 3]  -- Matches the non-empty list case
    
         

Pattern matching with case expressions is flexible, allowing behavior to be defined for various structures of data.

7.9 Operator E expressions

Operators like +, -, *, and == are used to perform arithmetic or logical operations.

Example:

            
let {
    sum = 1 + 2;
    isEqual = 5 == 5
} in
    (sum, isEqual)  -- Returns (3, True)
    
         

Operator expressions are syntactic sugar for function applications. For example, x + y is equivalent to add x y where add is the operator function.

7.10 Application of custom dayta types

Expressions involving user-defined types allow for rich data manipulation. For instance, a custom data type can be used in pattern matching and function application.

Example:

            
data Maybe a = Nothing | Just a;

let {
    describeMaybe m = case m of {
        Nothing -> "Nothing";
        Just x -> "Just " + show x
    }
} in
    describeMaybe (Just 42)  -- Matches the 'Just' case
    
         

User-defined types are central to IvoryScript’s flexibility in structuring data.

7.11 Conclusion

The expression forms in IvoryScript provide the building blocks for defining logic, performing calculations, and manipulating data. From simple literals and variable bindings to more advanced constructs like pattern matching and user-defined types, expressions form the core of writing functional programs in IvoryScript.

8 Coercion and type casting

8.1 Introduction to coercion

In IvoryScript, coercion and type casting are essential mechanisms that ensure expressions conform to expected types, with a focus on small steps of graph reduction rather than full evaluation. Coercion is applied automatically when values are transferred between contexts, such as when passing function arguments or defining names.

When an expression requires type adaptation, IvoryScript represents it as:

            
(COERCE, expr)
    
         

During type checking, IvoryScript attempts to match the type of expr with the expected type. If the types align, the coercion is no longer needed, and the expression is used directly. Otherwise, the expression is transformed using a cast:

            
(!)(cast expr)
    
         

This transformation introduces the necessary graph reduction steps for cases where type adaptation is required, ensuring the expression can be used in the given context.

8.2 The General Cast Class

IvoryScript uses the Cast class as the general mechanism for handling all type conversions. Instances of the Cast class define how an expression of one type can be cast to another. While there are many types of casts, the most common involves graph reduction, as function application and all constants are lazy by default.

The instance that deals with reductions is defined as follows:

            
instance Cast (Exp a), a where {
    inline cast x = (!)(reduceCast x)
}
    
         

This instance specifically handles the conversion from an expression of type Exp a to type a. The reduceCast function applies graph reduction to x, ensuring that the expression is reduced to a value before it is used. The explicit ! operator signals that the expression should be reduced as part of this casting process. Given that function application and constants are lazy, this cast is frequently encountered in practice.

8.3 Casting Between Strict Types

Casting between strict types, such as from Int to Double, is also handled by the general Cast class. However, for strict types, the transformation is direct and does not involve reduction. For example, casting from Int to Double is achieved using the following instance of StrictCast:

            
primitive fromIntDouble :: Int -> Double;

instance StrictCast Int, Double where {
    inline strictCast x = fromIntDouble x
}
    
         

In this case, the StrictCast instance uses a primitive function, fromIntDouble, to cast the value directly. This transformation occurs without coercion or reduction, as the cast involves only a straightforward type conversion between two strict types.

8.4 Transforming Strict Values to Lazy Expressions

IvoryScript also supports casting from strict values to lazy expressions, such as from a strict type a to Exp a. This conversion is handled using the general Cast class and is performed via instances of StrictCast that convert strict values into lazy expressions.

            
-- mkExpr :: a -> Expr a;
instance StrictCast a, Exp a | a ¬= Void where {
    inline strictCast x = mkExpr x
}
    
         

In this instance, the strict value is wrapped in a lazy expression of type Exp a using the mkExpr function. This cast is essential for supporting deferred computation and graph reduction in IvoryScript. The qualifier a¬= Void ensures that this transformation is valid for types that have representable values, excluding Void.

8.5 Summary

Coercion and type casting in IvoryScript are critical for managing type transformations while ensuring that expressions conform to expected types. The Cast class serves as the general mechanism for all conversions, and reduction is the most common type of cast due to the laziness of function application and constants:

  • The Cast class handles all type conversions, including strict type casts and transformations between expressions and values.

  • Instances of Cast (Exp a), a involve reduction using the reduceCast function, which reduces the expression step by step as required. This is the most frequently encountered cast.

  • Casting between strict types, such as from Int to Double, involves direct conversion without reduction.

  • Strict values can be transformed into lazy expressions, enabling deferred computation and graph reduction, through instances of StrictCast.

9 The 'let' Construct in IvoryScript

9.1 Introduction to 'let'

The let construct in IvoryScript is a fundamental building block for defining local variables and functions. It provides a way to bind expressions to names within a particular scope, allowing for modularity and reusability in code. The let construct is versatile, supporting both sequential and mutually recursive definitions.

9.2 Basic 'let' Usage

The most common usage of let involves binding expressions to names. These bindings are local to the block in which they are defined, and they are evaluated in sequence. Here is a basic example:

            
let {
    x = 10;
    y = x * 2;
    z = y + 5
} in
   z
    
         

In this example, the variables x, y, and z are defined locally within the let block, and the result of the expression is the final value of z. The variables are evaluated in the order they are written, making let a simple and powerful tool for local definitions.

9.3 Let and Function Definitions

The let construct also supports function definitions within its scope. This allows for defining local functions that can operate on the values bound in the same let block. Here's an example of a let block defining a function:

            
let {
    add x y = x + y;
    result = add 5 10
} in
    result
    
         

In this case, the function add is defined locally within the let block, and is then used to calculate the value of result. This allows for encapsulating functionality within the scope of the let expression.

9.4 Let and Recursive Functions

The let construct in IvoryScript also supports recursive and mutually recursive function definitions. This enables defining functions that refer to themselves or to each other within the same let block. Here's an example of a mutually recursive function pair:

            
let {
    isEven n = if n == 0 then True else isOdd (n - 1);
    isOdd n = if n == 0 then False else isEven (n - 1)
} in
    isEven 4
    
         

Here, the functions isEven and isOdd call each other, making them mutually recursive. Both are defined within the same let block, allowing for their mutual recursion to be expressed cleanly.

9.5 Evaluation Order in 'let'

In IvoryScript, the evaluation order within a let block is strictly sequential. This means that each expression is evaluated in the order it is written, and the result is bound to the corresponding name. If a variable depends on a previous definition, that previous expression must have been fully evaluated before the dependent variable can be used. This ensures a clear and predictable evaluation flow, avoiding potential ambiguities in execution.

However, if a function is defined within the let block and it is not immediately invoked, the function itself is treated as a value that can be evaluated later. This introduces the concept of delayed evaluation, where the function's body is not executed until the function is called. This behavior is particularly important when dealing with recursive or mutually recursive functions, as discussed in the next section.

9.6 Delayed Initialization of Closures in Mutually Recursive Functions

A significant challenge with the let construct arises when dealing with mutually recursive functions, especially in terms of the availability and initialisation of closures. When two or more functions are mutually recursive, the closure (the environment capturing the function's variables and references) for each function may not be fully initialised at the time the other functions try to reference it.

In practical terms, this means that while the names of mutually recursive functions are available within the same let block, their closures may be incomplete when one function tries to call the other. This delayed initialisation can result in runtime errors or undefined behavior if a function is invoked before its closure has been fully initialised.

For example, consider this mutually recursive pair of functions:

            
let {
    f x = if x == 0 then 1 else g (x - 1);
    g y = if y == 0 then 0 else f (y - 1)
} in
    f 5
    
         

In this example, f and g are mutually recursive, meaning they call each other. However, if the function g is invoked before its closure has been fully initialised (due to f calling it), it could result in incorrect behavior. To handle this, IvoryScript currently ensures that the names of these mutually recursive functions are available, but the full initialisation of their closures may be delayed until the functions are first invoked.

This is a key consideration when working with mutually recursive functions in IvoryScript. A common workaround is to structure recursive functions carefully, ensuring that any function that relies on a mutually recursive closure is invoked only after it has been fully initialised. Another approach is to split the mutually recursive functions across separate let blocks or modules, ensuring that each function's closure is fully constructed before mutual recursion occurs.

10 Polymorphic Primitive Constructor: Ptr a

IvoryScript provides the polymorphic constructor Ptr a for working with pointer values. These pointers allow references to values of any type a, primarily for operations that involve genuine state changes, such as interacting with mutable data structures.

10.1 Efficient Usage of Ptr a

Although it is possible to use Ptr a to implement structures like loops that modify state, this approach is generally inefficient compared to tail-recursive functional solutions. In IvoryScript, recursion is typically more efficient and maintains functional purity.

            
let table = HashTable 100
hashTableAdd table "key" "value"
let value = hashTableGet table "key"
      
         

10.2 When to Use Ptr a

The use of Ptr a should be limited to scenarios where state mutation is necessary. In most cases, a recursive approach is preferred for control flow, as it is functionally pure and efficient.

11 Tail Recursion in IvoryScript

11.1 How Tail Recursion Works

In IvoryScript, tail recursion is a technique used to optimize recursive functions by ensuring that the recursive call is the final operation in the function. If a function’s return value is a direct result of a recursive call without any additional operations, it is considered tail recursive.

Tail recursion allows the compiler or runtime to reuse the current function’s stack frame for the next recursive call, preventing the need to create new stack frames for each call. This results in efficient recursion with constant memory usage, similar to iterative loops.

let rec sum n = 
  if n == 0 then 0 else n + sum (n - 1)
         

This function is not tail recursive because the recursive call sum (n - 1) is followed by an addition. The function cannot return directly from the recursive call; it must complete the addition first.

11.2 Tail Recursive Example

To make the function tail recursive, the operation must be included in the recursive call. This is often done by introducing an accumulator:

let rec sum_tail n acc = 
  if n == 0 then acc else sum_tail (n - 1) (acc + n)
         

In this version, the recursive call is the last operation in the function, making it tail recursive. The accumulator acc carries the result forward, allowing the function to be optimized and avoid new stack frames.

11.3 Restructuring Functions for Tail Recursion

To achieve tail recursion, a function may need to be restructured. The goal is to ensure that the recursive call is the final action in the function. This often involves introducing an additional parameter, such as an accumulator, to carry intermediate results. Here’s how a typical non-tail-recursive function might be restructured:

let rec factorial n = 
  if n == 0 then 1 else n * factorial (n - 1)

This function can be restructured into a tail-recursive version by using an accumulator:

let rec factorial_tail n acc = 
  if n == 0 then acc else factorial_tail (n - 1) (acc * n)

In this tail-recursive version, the recursive call is the final operation, and the intermediate result is carried through the accumulator.

11.4 Inline Polymorphic Functions and Tail Recursion

In IvoryScript, inline polymorphic functions require special handling to achieve tail recursion. Because inline polymorphic functions are inlined directly, a tail-recursive function may need to delegate to a non-inline helper function. This helper function performs the actual recursion.

An example of this can be seen in the lengthList function, which computes the length of a list:

lengthList :: [a] -> Int;
inline lengthList l =
   let { lengthList_ :: [a] -> Int -> Int;
         lengthList_ l acc =
            case l of {
               [] ->             acc;
               _ :+ xs ->   lengthList_ ((!)xs) (acc + 1)
            }
   } in
      lengthList_ l 0;

The function lengthList is an inline polymorphic function, but the actual recursion occurs in the helper function lengthList_, which uses an accumulator to make the recursion tail recursive. The recursive call lengthList_ ((!)xs) (acc + 1) is the final operation, ensuring that the function remains efficient and tail-recursive while allowing the polymorphic behavior to be maintained.

11.5 Advantages of Tail Recursion

Tail recursion offers several key advantages:

  • Improved performance: Reusing the stack frame for each recursive call reduces the overhead of recursion and leads to faster execution.

  • Avoidance of stack overflow: Tail-recursive functions can handle deep recursion without risking stack overflow, making them more reliable for large data sets.

  • Memory efficiency: Since tail-recursive functions do not accumulate stack frames, they use constant memory, even for complex or deeply recursive operations.

11.6 Future Steps: Program Analysis for Tail Recursion

While tail recursion must currently be implemented manually in IvoryScript, future enhancements are planned to introduce program analysis that can automatically detect and transform functions into their tail-recursive equivalents. This would allow the compiler to optimize recursion without requiring explicit restructuring by the developer.

For now, restructuring functions for tail recursion requires manual refactoring to ensure the recursive call is the final action, using accumulators or helper functions as necessary.

12 Garbage Collection

12.1 Mark_GC Class

The Mark_GC class in IvoryScript specifies how custom types are marked during garbage collection. It defines the essential method mark_GC, which must be implemented for each custom type.

The mark_GC method ensures that all reachable objects of the specified type are correctly processed by the garbage collector. This method traverses the structure of the object, marking any nested or referenced values, which is essential for preventing the reclamation of live objects during memory management.

class Mark_GC a where
    mark_GC :: a -> ()

instance Mark_GC MyType where
    mark_GC v = case v of
        MyType x y -> {
            mark_GC x;
            mark_GC y;
        }

In this example, the mark_GC function uses a case expression to deconstruct MyType, marking each of its fields to ensure all components are handled during garbage collection.

12.2 Future Derivation from Data Constructor Representation

Although every custom type currently requires an explicit Mark_GC instance, future developments are expected to allow automatic derivation of the mark_GC function based on the data constructor's representation function. This would simplify the implementation process, enabling IvoryScript to automatically generate the necessary marking logic for most types without manual intervention.

This enhancement would further streamline the garbage collection process and reduce the need for repetitive code in custom type definitions.

13 Advanced Use of IvoryScript for Embedded Applications with Limited Resources

13.1 Introduction

IvoryScript’s design is particularly suited for embedded systems operating under constraints on memory, processing power, and communication bandwidth. This chapter explains how IvoryScript can minimize resource usage by separating the compilation process, using remote bytecode interpretation, and supporting dynamic linking to the runtime system. It also explores how applications can evolve dynamically while remaining available for inspection and modification, enabling flexible deployment in various constrained environments, including unmanned systems like drones or spacecraft.

13.2 Lightweight Deployment Without the Compiler

In embedded systems where resources are constrained, such as in drones, vehicles, or satellites, the IvoryScript compiler can be omitted entirely. This reduces the memory footprint and computational overhead, as the only necessary components are the bytecode interpreter and runtime system.

  • Local Compilation and Bytecode Preparation: Order scripts are prepared on a local host with full compiler support. These scripts are verified, checked for correctness, and compiled into bytecode. The resulting bytecode, which is compact and efficient, is then ready for transmission to the embedded device.

  • Remote Bytecode Transmission and Interpretation: The compiled bytecode is transmitted to the embedded system, such as a drone or remote sensor platform. The lightweight interpreter on the device executes the bytecode, dynamically linking it to the runtime system as needed. There is no need to reboot or restart processes to execute new Order scripts, allowing updates to happen seamlessly in real-time.

13.3 Dynamic Linking to the Runtime System

IvoryScript’s ability to dynamically link new bytecode to the runtime system allows the application to evolve over time without a full reboot or recompilation. This feature is particularly useful in environments where requirements may change mid-operation, and new functionalities need to be introduced dynamically.

  • Modular Runtime System: The runtime system on the embedded device can accept new bytecode modules, linking them into the application as needed. This modular approach allows for incremental updates without disrupting existing functionality.

  • Small Memory Footprint: Only necessary components are loaded into memory, ensuring that resources are conserved. Unused parts of the system can be removed, keeping the memory footprint minimal.

  • Remote Inspection and Modification: IvoryScript supports runtime object inspection, enabling developers to examine and modify objects remotely. This is invaluable for adjusting configurations or troubleshooting during deployment, allowing the system to be monitored and evolved in real-time.

13.4 Evolving Applications and Dynamic Object State

IvoryScript enables applications to adapt and evolve during operation, which is particularly beneficial for systems deployed in inaccessible locations or with long operational lifetimes. The system can be updated dynamically as new operational requirements arise, all while minimizing communication bandwidth and avoiding re-deployment.

  • On-Demand Updates: As operational conditions change or unexpected events occur, updated functionality can be transmitted in bytecode form, dynamically linked into the runtime system. This allows the application to adjust without interrupting ongoing operations.

  • Object State Evolution: Throughout operation, the state of application objects—such as system configurations or sensor data—can be inspected and modified remotely. For example, sensor calibration data can be updated as conditions change, ensuring optimal system performance without requiring system-wide changes.

  • Efficient Bandwidth Use: Bytecode is compact, minimizing the data needed for updates and allowing the system to evolve even in environments with limited communication bandwidth, such as remote drones or sensor networks.

13.5 Key Use Cases in Remote and Embedded Systems

IvoryScript’s capabilities are particularly well-suited for a range of embedded applications, especially those in constrained environments like drones, autonomous vehicles, or other remote systems.

  • Mission Updates in Real Time: IvoryScript enables real-time system adaptations by transmitting updated scripts that allow drones or other autonomous systems to switch tasks or adapt to new operational phases without a full system reset.

  • System Adaptation to New Conditions: In the event of unforeseen conditions (e.g., equipment malfunctions or environmental changes), new scripts can be sent to update operational behavior in real-time, such as adjusting power management routines or navigation protocols.

  • Remote Troubleshooting and Modification: The ability to inspect and modify running systems remotely provides flexibility to handle software issues during operation, potentially extending the operational lifetime of drones, remote sensors, or other systems.

13.6 Conclusion

IvoryScript’s ability to separate compilation from execution, combined with its dynamic linking and runtime inspection features, makes it an ideal language for embedded applications with limited resources. By relying on remote bytecode interpretation and dynamic system evolution, IvoryScript allows for ongoing adaptation while keeping memory and bandwidth demands low. This flexibility ensures that systems can evolve over time and remain operational in various resource-constrained environments, whether on drones, sensors, or other embedded platforms.