Skip to content
Ridgun
English

Ridgun

The language itself: a compiler that compiles itself, the stable project tool, the specification and the book

05-lists-tuples-and-sequences.md178 lines · 9.1 KBRaw
1# Chapter 05: Lists, Tuples, and the Ninja Trick of Sequences
2
3A program that only moves one number or one string at a time doesn't get you very far. In the real world you handle lists of files, database rows, network packets, or coordinates.
4
5For that, Ridgun gives you three distinct containers, each designed for a very specific job:
61. **Lists (`[T]`)**: *"I have many elements of the same type (or none at all) and I want to traverse them"*.
72. **Tuples (`(A, B)`)**: *"I want to return a pair or trio of different things together without declaring a new type"*.
83. **Sequences and masks (`s(_ + _)`, `(+ _ _ +)`)**: *"I want to filter or slice arguments on the fly as if I had a mathematical sieve"*.
9
10Let's take them one by one.
11
12---
13
14## 5.1. Lists (`[T]`): Your Daily Bread
15
16In Ridgun, wrapping square brackets around a type gives you a list of that type:
17- `[Int]` is a list of integers: `[10, 20, 30]`.
18- `[String]` is a list of strings: `["apple", "pear"]`.
19- `[]` is the empty list (the wildcard that fits any `[T]`).
20
21> *"Hey, can I put a number and a string together in the same list like `[1, "two"]`?"*
22> Raw like that, **no** (and thank the compiler for stopping you, because that guarantees an addition will never blow up in production when it stumbles upon a surprise string). In Chapter 06 we will see how to model `[1, "two"]` with total elegance using an Algebraic Data Type (just like Haskell and Rust do).
23
24### How to Slice Up a List Without `i`, `j`, `k` Indices: `[]` and `(x:xs)`
25In imperative languages, walking a list usually means `for (int i = 0; i < list.size(); i++)`, carrying the classic risk of an *off-by-one* error.
26
27In Ridgun you don't count indices; **you take the list apart from the head**:
28- `[]`: matches when the list is empty.
29- `(x:xs)`: matches when the list has at least one element. It binds the first element to `x` (the **head**) and the rest of the list to `xs` (the **tail**).
30
31Look how satisfying it is to sum a list or count how many elements it has:
32
33```ridgun
34sum list [Int] => Int
35sum list [] = 0
36sum list (x:xs) = x + (sum list xs)
37
38count elements [A] => Int
39count elements [] = 0
40count elements (_:xs) = 1 + (count elements xs)
41```
42
43And here is a technical detail that will make your day: in Ridgun, taking the tail `xs` in `(x:xs)` **does not copy the rest of the list in memory**. Internally it is a zero-copy view over the same backing vector that simply advances the start offset by `1` (instantaneous $O(1)$).
44
45### Peeling Off Multiple Elements at Once
46The `:` pattern shines when you are parsing commands or network protocols:
47
48```ridgun
49parse command [String] => String
50parse command [] = "You didn't type anything"
51parse command ("help":_) = "Showing help..."
52parse command ("copy":src:dst:[]) = "Copying from {src} to {dst}"
53parse command (cmd:_) = "Unknown command: {cmd}"
54```
55
56Look at the third row: `("copy":src:dst:[])` verifies in a single stroke that the list has **exactly 3 elements**, that the first one is `"copy"`, and hands you the second and third elements on a silver platter in `src` and `dst`. Try writing that in C or Java and count how many lines of `args.length == 3 && args[0].equals("copy")` you end up with.
57
58### Joining Lists with Double Colon (`::`)
59When you are on the right-hand side of `=` (in an expression) and want to concatenate two lists, use **`::`**:
60
61```ridgun
62join lists [Int] [Int] => [Int]
63join lists a b = a :: b
64
65append item [Int] Int => [Int]
66append item xs x = xs :: [x]
67```
68
69*(Quick mnemonic rule: a single `:` in patterns takes a list apart; a double `::` in expressions glues two lists together).*
70
71And what if you genuinely want to grab element number 5 directly without traversing anything? Use square brackets right after the name: `my_list[5]`.
72
73---
74
75## 5.2. Variadic Functions (`T...`): "Pass Me as Many as You Want!"
76
77Sometimes you want callers of your function to write `max of 4 19 2 88 7` comfortably, without forcing them to wrap the numbers in square brackets `[4, 19, 2, 88, 7]`.
78
79Put three dots **`...`** after the last type in your signature, and inside your function you will automatically receive those arguments packaged as a list:
80
81```ridgun
82fn :: sum many Int... => Int
83 sum many nums = sum list nums
84
85fn :: main => ()
86 main =
87 echo (sum many 10 20 30) -- Prints 60
88 echo (sum many 1 2 3 4 5 6 7) -- Prints 28
89```
90
91---
92
93## 5.3. Tuples and Unified Space-Separated Type Signatures (`Int Int [String String] String`)
94
95In many languages, returning two or more values from a function is a headache: either you create a throwaway helper class `ResultWithPosition`, or you clutter the signature with redundant commas and parentheses.
96
97In Ridgun, **an argument list `Int String Int` and a tuple `(Int, String, Int)` are conceptually the same thing: a typed sequence (`seq`)**. Because of this, in type signatures you can separate tuple elements simply with **spaces**—after `=>`, inside list brackets `[...]`, and inside parentheses `(...)`:
98
99```ridgun
100-- Two return integers separated by a space:
101fn :: div rem Int Int => Int Int
102 div rem a b = (a / b, a % b)
103
104-- Complex return signature without comma or outer-parenthesis clutter:
105fn :: parse header String => Int Int [String String] String
106 parse header tag = (200, 2, [("Host", "ridgun.org"), ("X-Tag", tag)], "OK")
107
108-- List of tuples [String String] as an input parameter:
109fn :: lookup header [String String] String => String
110 lookup header [] _ = "missing"
111 lookup header ((k, v):rest) key
112 | k == key = v
113 | otherwise = lookup header rest key
114```
115
116Notice how clean `=> Int Int [String String] String` is:
117- After `=>`, `Int Int [String String] String` is 100% equivalent to `(Int, Int, [(String, String)], String)`.
118- Inside brackets, `[String String]` denotes a list of string pairs `[(String, String)]`.
119- Inside parentheses (when passing a tuple as a single grouped parameter), `(Int Int)` is 100% equivalent to `(Int, Int)`:
120 ```ridgun
121 fn :: explain division (Int Int) => String
122 explain division (quotient, remainder) = "Fits {quotient} times with {remainder} left over"
123 ```
124- **Why does this have zero syntactic ambiguity in Ridgun?** Unlike Haskell (where `Maybe Int` applies a type constructor by juxtaposition), parameterized types in Ridgun use explicit parentheses like `Tensor(Float, 3, 3)` and lists use `[T]`. Therefore, two space-separated types (`T1 T2`) always unambiguously denote a tuple/sequence of types while remaining 100% compatible with comma syntax.
125
126### The Star Feature: Automatic Tuple Unpacking Between Functions
127Suppose you have `div rem` (which returns `Int Int`) and another function `add pair Int Int => Int` (which expects 2 separate integers). In Ridgun you can plug one function straight into the other:
128
129```ridgun
130fn :: add pair Int Int => Int
131 add pair x y = x + y
132
133fn :: main => ()
134 main =
135 -- div rem 17 5 returns (3, 2), and add pair receives 3 and 2 directly:
136 echo (add pair (div rem 17 5)) -- Prints 5
137```
138
139---
140
141## 5.4. The Ninja Trick: Sequences and Positional Masks (`s(_ + _)`, `(+ _ _ +)`)
142
143Here comes one of Ridgun's most original gems, inherited straight from array languages like J and APL.
144
145Imagine you receive a stream of arguments using the sequence pattern **`(s)`** and want to keep only certain positions while discarding others, without inventing five throwaway variable names (`_a`, `b`, `_c`, `d`...).
146
147You apply a **tuple mask of `+` (keep it) and `_` (drop it)** to `s`:
148
149- **`s(_ + _)`**: out of 3 elements, drop the first (`_`), keep the second (`+`), and drop the third (`_`).
150- **`s(+ _ +)`**: keep the first and the third; returns the sequence containing those two.
151- **`s(_ _ +...)`**: drop the first two (`_ _`) and apply `+...` **to all remaining elements that follow**, no matter how many there are.
152
153See it in action:
154
155```ridgun
156fn :: middle of three Int Int Int => Int
157 middle of three (s) = s(_ + _)
158
159fn :: drop first two Int... => [Int]
160 drop first two (s) = s(_ _ +...)
161
162fn :: main => ()
163 main =
164 echo (middle of three 100 42 999) -- Prints 42
165 echo (drop first two 1 2 10 20 30) -- Prints [10, 20, 30]
166```
167
168And the best part: you can plug a positional mask **`(+ _ _ +)`** **right inside an idiomatic `->` pipeline**! When a function returns a tuple of several values and the next function only needs two of them, filter the tuple on the fly and Ridgun will unpack the surviving elements directly as separate arguments into the next stage:
169
170```ridgun
171-- parse packet returns 4 values: (Int, Int, [String String], String)
172-- (+ _ _ +) keeps the 1st (code) and the 4th (msg), and unpacks them into combine code:
173parse packet "v0.9.9" -> (+ _ _ +) -> combine code -> summary
174```
175
176On top of that, if you use the comma operator `,` outside parentheses (`list1 , list2`), Ridgun flattens both collections into a single continuous sequence.
177
178In **Chapter 06** we will take the step that turns a functional programmer into a first-class software architect: **creating your own Algebraic Data Types (ADTs), writing polymorphic functions with `A`..`Z`, and crafting your own custom operators**.