00_Introduction

Tutorial: Learning the Scripting Language

This guide will teach you how to use this functional programming language, assuming you have basic programming knowledge and a passing familiarity with functional programming concepts.

What You'll Learn

By the end of this tutorial, you'll be able to:

  • Write basic programs with functions and data
  • Use pattern matching for conditional logic (our only control flow)
  • Work with tables (our only data structures)
  • Apply functional programming patterns
  • Use the standard library's combinators

Getting Started

Running Your First Program

Create a file called hello.txt with this content:

/* Your first program */
..out "Hello, World!";

Run it with:

node lang.js hello.txt

You should see: Hello, World!

Basic Values and Variables

The language supports numbers, strings, and booleans:

/* Basic values */
name : "Lucy Snowe";
age : 18;
is_student : true;

/* Output values */
..out name;
..out age;
..out is_student;

Key Point: Variables are immutable - once assigned, they cannot be changed.

Functions: The Building Blocks

Defining Functions

Functions are defined using arrow syntax:

/* Simple function */
double : x -> x * 2;

/* Function with multiple parameters */
add : x y -> x + y;

/* Using functions */
result : double 5;
sum : add 3 4;
..out result;  /* Output: 10 */
..out sum;     /* Output: 7 */

Function Application

Functions are applied by putting the function name followed by arguments:

/* Function application */
square : x -> x * x;
result : square 5;
..out result;  /* Output: 25 */

/* Multiple applications */
double : x -> x * 2;
increment : x -> x + 1;
result : increment (double 5);
..out result;  /* Output: 11 */

Key Point: Unary minus works without parentheses: f -5 applies f to negate(5). Use spaces around binary operators for clarity: 5 - 3 for subtraction. See the Juxtaposition tutorial for detailed information about operator spacing.

Pattern Matching with when

Instead of if/else statements, we use pattern matching:

/* Basic pattern matching */
classify : x -> 
  when x is
    0 then "zero"
    1 then "one"
    _ then "other";

/* Using the function */
..out (classify 0);  /* Output: "zero" */
..out (classify 1);  /* Output: "one" */
..out (classify 5);  /* Output: "other" */

The _ is a wildcard that matches anything.

Multiple Value Patterns

You can match on multiple values:

/* Multiple value patterns */
compare : x y -> 
  when x y is
    0 0 then "both zero"
    0 _ then "x is zero"
    _ 0 then "y is zero"
    _ _ then "neither zero";

/* Using the function */
..out (compare 0 0);  /* Output: "both zero" */
..out (compare 0 5);  /* Output: "x is zero" */
..out (compare 3 0);  /* Output: "y is zero" */
..out (compare 3 5);  /* Output: "neither zero" */

Tables: Our Data Structures

Tables are like objects or dictionaries in other languages:

/* Creating tables */
person : {name: "Alice", age: 30, city: "NYC"};
numbers : {1, 2, 3, 4, 5};

/* Accessing values */
..out person.name;
..out person["age"];
..out numbers[1];  /* Note: indexing starts at 1 */

Table Operations

Tables support element-wise operations:

/* Transform every value in a table */
double : x -> x * 2;
numbers : {1, 2, 3, 4, 5};
doubled : map @double numbers;
..out doubled[1];  /* Output: 2 */
..out doubled[2];  /* Output: 4 */

/* Filter values in a table */
is_even : x -> x % 2 = 0;
evens : filter @is_even numbers;
..out evens[2];  /* Output: 2 */
..out evens[4];  /* Output: 4 */

Key Point: The @ symbol creates a function reference, which is needed for higher-order functions.

Function Composition

Combining Functions

You can combine functions to create new ones:

/* Function composition */
double : x -> x * 2;
increment : x -> x + 1;

/* Right-to-left composition (like the (mostly) regular mathematical style) */
double_then_increment : compose @increment @double;
result : double_then_increment 5;
..out result;  /* Output: 11 (5*2=10, then 10+1=11) */

/* Left-to-right composition (pipeline style) */
increment_then_double : pipe @increment @double;
result : increment_then_double 5;
..out result;  /* Output: 12 (5+1=6, then 6*2=12) */

The via Operator

The language has a special via operator for composition:

/* Using the via operator */
double : x -> x * 2;
increment : x -> x + 1;
square : x -> x * x;

/* This is equivalent to compose */
result : double via increment via square 3;
..out result;  /* Output: 20 (3^2=9, 9+1=10, 10*2=20) */

Working with Multiple Tables

Element-wise Operations

The each combinator lets you combine multiple tables:

/* Element-wise addition */
table1 : {a: 1, b: 2, c: 3};
table2 : {a: 10, b: 20, c: 30};
sum : each @add table1 table2;
..out sum.a;  /* Output: 11 */
..out sum.b;  /* Output: 22 */
..out sum.c;  /* Output: 33 */

/* Adding a scalar to every element */
numbers : {1, 2, 3, 4, 5};
incremented : each @add numbers 10;
..out incremented[1];  /* Output: 11 */
..out incremented[2];  /* Output: 12 */

Immutable Table Operations

The t. namespace provides immutable table operations:

/* Creating and modifying tables */
person : {name: "Alice", age: 30};

/* Immutable update */
updated : t.set person "age" 31;
..out updated.age;  /* Output: 31 */
..out person.age;   /* Output: 30 (original unchanged) */

/* Immutable merge */
updates : {age: 32, city: "NYC"};
merged : t.merge person updates;
..out merged.age;   /* Output: 32 */
..out merged.city;  /* Output: "NYC" */
..out merged.name;  /* Output: "Alice" */

/* Safe access with defaults */
name : t.get person "name" "Unknown";
city : t.get person "city" "Unknown";
..out name;  /* Output: "Alice" */
..out city;  /* Output: "Unknown" */

Recursive Functions

Functions can call themselves:

/* Factorial function */
factorial : n -> 
  when n is
    0 then 1
    _ then n * (factorial (n - 1));

/* Using factorial */
..out factorial 5;  /* Output: 120 */
..out factorial 0;  /* Output: 1 */

Practical Examples

Data Processing Pipeline

/* Processing a list of numbers */
numbers : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

/* Filter even numbers, double them, then sum */
is_even : x -> x % 2 = 0;
double : x -> x * 2;

/* Pipeline: filter -> map -> reduce */
evens : filter @is_even numbers;
doubled : map @double evens;
total : reduce @add 0 doubled;

..out total;  /* Output: 60 (2+4+6+8+10)*2 = 60 */

Table Transformation

/* Working with structured data */
people : {
  alice: {name: "Alice", age: 30, city: "NYC"},
  bob: {name: "Bob", age: 25, city: "LA"},
  charlie: {name: "Charlie", age: 35, city: "Chicago"}
};

/* Extract all ages */
get_age : person -> person.age;
ages : map @get_age people;
..out ages.alice;   /* Output: 30 */
..out ages.bob;     /* Output: 25 */

/* Find people over 30 */
is_over_30 : person -> person.age > 30;
seniors : filter @is_over_30 people;
..out seniors.charlie.name;  /* Output: "Charlie" */

Common Patterns

Partial Application

Functions can be partially applied:

/* Creating specialized functions */
add : x y -> x + y;
add_ten : add 10;

/* Using the specialized function */
..out (add_ten 5);  /* Output: 15 */
..out (add_ten 20); /* Output: 30 */

Function References

Use @ to pass functions as arguments:

/* Higher-order functions */
apply_twice : f x -> f (f x);
double : x -> x * 2;

/* Using apply_twice */
result : apply_twice @double 3;
..out result;  /* Output: 12 (3*2=6, 6*2=12) */

Debugging and Testing

Assertions

Use assertions to test your code:

/* Testing your functions */
double : x -> x * 2;
..assert (double 5) = 10;
..assert (double 0) = 0;
..assert (double (-3)) = -6;

..out "All tests passed!";

Debug Output

Add debug output to understand what's happening:

/* Debugging a function */
process_data : x -> {
  ..out "Processing:";
  ..out x;
  result : x * 2;
  ..out "Result:";
  ..out result;
  result
};

final : process_data 5;
..out "Final result:";
..out final;

Best Practices

Break Down Complex Operations

/* Complex operation broken down */
data : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

/* Step 1: Filter */
is_even : x -> x % 2 = 0;
evens : filter @is_even data;

/* Step 2: Transform */
square : x -> x * x;
squared : map @square evens;

/* Step 3: Aggregate */
total : reduce @add 0 squared;
..out total;

Use Pattern Matching for Conditionals

/* Good: Pattern matching */
classify : x -> 
  when x is
    0 then "zero"
    1 then "one"
    _ then "other";

Embrace Immutability

/* Good: Immutable operations */
person : {name: "Alice", age: 30};
updated : t.set person "age" 31;
/* person remains unchanged */

/* Avoid: Trying to modify existing data,
   this language doesn't support mutation */

Next Steps

You now have a solid foundation in the scripting language! Here are some areas to explore:

  1. Advanced Pattern Matching: Complex patterns and nested matching
  2. Table Comprehensions: Building tables from other data
  3. Function Composition: Building complex transformations
  4. Error Handling: Working with edge cases and invalid data
  5. Performance: Understanding how the language executes your code

For a deep dive into combinators and advanced problem-solving patterns, check out the Combinators Deep Dive tutorial.

The language is designed to be functional and expressive. As you practice, you'll find that many operations become more natural when you think in terms of data transformations rather than step-by-step instructions.

Happy coding!