A language tutorial by Laurie Cheers.

Welcome to Swym. This programming language is designed to let you Say What You Mean, as clearly and elegantly as possible. (It's pronounced "swim", in case you were wondering.)

I designed Swym in a crazy way - I wrote down some programs I wanted to be able to write, regardless of whether they were implementable; and then I reverse-engineered semantics for them. That process showed me how I needed to change the programs, to make them behave more consistently, and to make them parseable. It took about five years of iterating like this, on and off, before the language finally started to gel enough to be implementable. I hope you like the results. If nothing else, the process seems to have given Swym a unique flavour.

The interpreter is currently implemented in Javascript. This isn't intended to be permanent - just for this proof-of-concept implementation. For the same reason, don't be surprised if you experience any slowdowns: at the moment this language isn't optimized at all. Seriously; not. at. all. I decided I preferred getting it working over getting it fast.

I wrote it in Javascript partly because I wanted to learn Javascript better, but mostly to let me embed a command line in this webpage, like this...


Click Run to see the output. (You'll need Javascript enabled on your browser, of course.)

Each time you click you're running the Swym interpreter, live in your web browser. Feel free to rewrite any of the examples on this page and run them to see what you get.

You can also click the +/- button to expand the text boxes, or click Revert if you've deleted the original text and want to get it back.


Contents

  1. The Basics
  2. Lists and strings
  3. Functions
  4. Multi-values
  5. Etc
  6. Patterns and lazy lists
  7. Future development

But hey, maybe you're in a hurry, and you just want to get a flavour of what the language is like. Well, here's a little buffet of code examples. Read on for full details...

Multi-values:


The etc keyword:


Patterns:


Lazy lists: (Try clicking this one more than once...)



Section 1 - The Basics

This part of a language tutorial is always tedious, so I'll try to make it quick. Swym is designed to be at least somewhat familiar to anyone who knows a C-like language, such as Java, C#, Javascript... or C itself, of course. Swym has the standard C-style comments (/* */ and //), character and string syntax ('X' and "Hello\nWorld!"), arithmetic operators (+ - * / % += -= *= /= %= ++ --), and logical operators (== != < > <= >= && || !), all with the standard precedence. All the basic stuff you see in all these languages. Control expressions also resemble the ones in C - for example, if(x){ return(x); } else { return(y); }. And yes, bring your semicolons. You'll need them.


(FYI, since this is implemented in Javascript, all numbers are implemented as double-precision floats. Thus the division above is floating-point division. I haven't decided whether I want to keep this behaviour permanently.)

Yes, function calls look pretty familiar too. Calling output(whatever) will cause the specified value to be shown in the output window (without halting your program). If your program halts without calling output at all, it'll output the result of the last expression. That's what was happening with the minimalist "Hello World" above.

For convenience, there's also a function print, which does the same as output, but adds a line break. Let's try declaring our own, to see what a function declaration looks like:


The colon operator is the way we declare things. The identifier to the left of the colon is the name being declared, and whatever's on the right is the value, or the function's body. Names are case sensitive. As you can see, when we declare a function this way its first argument is implicitly called this.

You'll notice that the definition starts with the word Value. That's the type signature of this function - in this case, not that interesting. Value is the universal type: it matches any value at all.

One major theme in Swym is its flexible syntax. Any single-argument function call can be written function-call style (myprint(x)), or member-access style (x.myprint) with no difference in meaning. You can also mix styles in a single expression (myprint(5.square)). This distinction is entirely aesthetic. I usually find a line is more readable when written in one way or the other.

Functions can also take more than one argument, but it would cause confusion if we allowed the same flexibility there (We can't have a.given(b) mean the same as given(a,b))! So a function with more than one argument must be called either prefix or infix style, whichever it was declared with.

One last note - x: "hello" above is declaring a constant. You can't modify it after the initial definition. x will contain the value "hello", forever. Swym does have variables too, but we need to introduce a few more concepts before we get to those.

Swym has five basic data types:

  1. Booleans (true and false)
  2. Numbers (such as 42 and -0.5)
  3. Characters (such as 'H' and '\n' - see Lists for more details.)
  4. Lists (such as [1,3,5,9] and "Hello" - see Lists.
  5. Functions (such as {it*it} - see Functions.)

There's one value that's not of the above types: null. This, and the boolean false are considered falsy to anything that cares (such as if). Everything else is considered truthy (including the number 0 and the empty string "" - be careful there).


Section 2 - Lists and Strings

In Swym, declaring a list of values looks much like an array in Javascript. You just write a series of values between square brackets. The values can be separated by either commas or semicolons. If you want you can also put a comma or semicolon after the last item; or not. (Again, these are just aesthetic choices). And naturally, [] is the empty list. (Or [,] or [;;,,,;,].)

Note that in Swym, square brackets always denote that a new list is being created. You can't use them to get elements from a list. Accessing elements of a list can be done with the function at - for example, mylist.at(0). (By default, the first element is at index 0.)

Strings don't have a special type of their own in Swym - any list of character values is a string. So most functions that operate on lists can also operate on strings:


(PS: two bits of syntactic sugar snuck in there. Firstly, if you're doing a function call such as Reversed( [1,3,5,9] ), you can save some typing by omitting the round brackets - Reversed[1,3,5,9]. A name followed by any type of open bracket denotes a function call. Incidentally, that's why Swym doesn't support indexing a list using square brackets: a[b] would denote calling the function a on the list [b].

And secondly, if you're doing a function call like print(Reversed("Hello")), you can save a little more typing by changing it into what I call "hyphenated style": print-Reversed("Hello"). It doesn't make that much difference here, but in more complex expressions it can be a nice way to avoid strings of close brackets... )))))).

PS: and yes, the minus sign can also mean subtraction. Here's the rule: a hyphenated function is a series of identifiers separated by minus signs and no whitespace, and there must be either (A) a dot at the beginning, (B) an open bracket at the end, or (C) at least two minus signs in the sequence. So a-b means subtraction, as does a - b(c). But a.b-c and a-b-c and a-b(c) are function calls. So, yeah - if you think this is weird... I know. It's just that sometimes the hyphenation makes the code so pretty! I couldn't resist it.

A useful trick for improving these hyphenated calls is to add calls to of. The of function is defined as Value.of: it; which is to say, it has no effect and simply returns its argument verbatim. of's only purpose is to improve readability: print-Stem-of-each-Partitioning-of-X.)

If every element in a list is a character, Swym will print the list as a string. Otherwise, it gets displayed as a comma-separated list of values. (One weird case: the empty list [] is equivalent to an empty string, and it gets printed as such.)

Swym supports string interpolation, as seen in Perl or Ruby: when you write $name in a string, you're inserting the contents of the value called name at that point. If you actually want a $ sign in your string, use \$.

Unlike Perl or Ruby, however, Swym also lets you write $foo.bar to call a postfix-style function within a string - or $(whatever) to run some arbitrary code. The latter is also useful to precisely mark the end of the interpolated section - for example, without it you couldn't insert a value that's immediately followed by a letter. "You're on the $(floor)th floor.".

Take it away, maestro...


(More syntactic sugar: Any function call like f(x,y) can also be written f(x)(y). So the case and for statements there are really just function calls: case( this,[0],{"no more bottles"},[1],{"1 bottle"} ), and for( 3..1, {...} ). On the other hand, else is a keyword - we'll go into that a bit later.)

This example will hopefully give you a bit more of a feel for the language. I won't explain every detail for now, but the basic logic should be clear enough: first we're defining a function called bottles, which takes an Int as a parameter (implicitly named this), and uses the function case to choose one of three possible templates to display that number of bottles. Then, to print each verse, we count down from 3 to 1 (and that number is implicitly called it), and we call it.bottles a few times to fill in the appropriate numbers of bottles. This also works if you replace the 3 with 99, of course.


Section 3 - Functions

In actual fact, there were five functions being defined in the example above - 'bottles', plus four anonymous functions. Swym's notation for declaring anonymous functions is very minimalist: just write any expression in curly brackets. Such a function takes one argument, called it, and returns the result of evaluating the expression. So {it*2}, for example, is a function that returns twice the number you pass to it.


The function for is a very simple one - it just takes a value, and a function, and calls that function with that value. Let's define our own, for the sake of it:


Something to note: we named the second parameter .f, not just f. If we hadn't put a dot at the beginning, we wouldn't have been able to call it using a function call.

Whichever function call syntax you use, a call to a function named foo is actually treated as a call to a function named .foo. Or to put it another way - functions have their own namespace in Swym. You can have a function called .foo and a value called foo, and they'll happily coexist. Whenever you write x.foo or foo(x) you'll be calling the function; any other way of referring to foo will refer to the value.


Which means that, yes, all you can do with a function named .foo is call it. In particular, you can't just refer to the function without calling it. This is pretty unusual in a functional language, but it turns out not to be a big problem. Instead of passing the function directly, you'll have to define a new anonymous function which calls .foo. You can pass anonymous functions around just fine.

As you can perhaps see, although Swym could certainly (semantically speaking) be classed as a functional language, it gently discourages the kind of mind-bogglingly abstract function-munging that sometimes goes on in languages like Haskell.


(This comes up so much, we have one more bit of syntactic sugar for it: writing .function is a shorthand for it.function.)

Aww, that wasn't so bad, was it? In case it's not obvious, .SortedBy is a function that takes a list, and a function, and returns the contents of the list, sorted by the results from that function. And .length is a function that takes a list, and returns the list's length.

Let's see some more predefined functions that work on lists:

P.at(n)The value at index n. (P.at(0) being the first.)
1st(P), first(P)The first value in the list P.
2nd(P)The second value in P.
5th(P)The fifth value in P.
53rd(P)The fifty-third value in P. (Etc. You get the idea.)
last(P)The last value in P.
2ndLast(P)The second-last value in P. (Etc.)
length(P)The number of values in P.
total(P)(on a list of numbers) All those numbers, added together.
Flattened(P)(on a list of lists) All those lists, concatenated into a single list.
min(P)The smallest value. (Or if several are equally small, the first of them.)
Min(P)A new list: all the equal-smallest values.
max(P)The largest value. (Or if several are equally large, the first of them.)
Max(P)A new list: all the equal-largest values.
random(P)Any value from P, selected at (pseudo)random.
Reversed(P)A new list: the contents of P, in reversed order.
Shuffled(P)A new list: the contents of P, in a random order.
Sorted(P)A new list: the contents of P, in ascending order.
Tail(P)A new list: all values in P except the first.
Middle(P)A new list: all values in P except the first and last.
Stem(P)A new list: all values in P except the last.
P.Each{f}A new list: the result of calling f on each element.
P.Where{f}A new list: the values in P for which f returns true.
P.SortedBy{f}A new list: the contents of P, sorted by the values returned by f.
P.withMin{f}A new list: the first value for which f returns the smallest value.
P.WithMax{f}A new list: all the values with the equal-largest value of f.

In particular, notice the naming convention - most functions start with an uppercase letter if they return a new list (i.e. multiple values), or lowercase if they return a single value. If a single-value function doesn't have a suitable value to return (for example if you write [5,8,3].10th to get the tenth element of a 3-element list), then it will return null.

Let's try out a couple of these functions.



Section 4 - Multi-values

And now for something completely different (different from every other language, that is). In most languages, an expression must return one and only one value. In Swym, an expression can return several values!

So, er, what does that mean? Well, one common use for it is to fill in the contents of a list. If you write a multi-valued expression in a list, the result will be a list with all those values inserted at that position.


This is demonstrating two of the simplest multi-value operators.

The .. operator returns a sequence of integers - starting with the left hand one, and ending with the right hand one. The sequence can be ascending or descending, and will include both endpoints. If you want a sequence that excludes one or both endpoints, and/or enforces an ascending or descending order, there are some related operators: ..< for example, which will exclude the right hand number - and also the left hand one, if it's greater than or equal to the right hand side. So 1..<0, for example, won't return any values at all. Other operators in the family are ..> <.. >.. <..< >..>.

And of course, we're already familiar with the simplest multi-value operator: the , operator just returns the value(s) from its left hand side, followed by the value(s) from its right hand side.

One particularly useful way of producing multiple values is the each function. (Not to be confused with the Each function). each takes a list, and literally returns each element of that list. It's the inverse of the list construction [] operator. For example:


This is a little more involved, so let's go through it step by step.

The first line is a function that takes a number, and generates a list of its nontrivial divisors (i.e. the numbers it's divisible by, except for 1 and itself). So for example, NontrivialDivisor-of(16) would return [2,4,8]. The function works by first generating a list [1<..this/2], which contains every positive integer greater than 1, up to and including half of the number in question (a little optimization there). Then, it calls Where to discard those which don't divide into the first number with remainder zero.

In particular, notice the code this%it - where this is the number we're generating the divisors for, and it is the list element we want to test.

The second line is a function that generates a list of all divisors for a number, both trivial and nontrivial. It does this by simply constructing a list containing 1, then (literally) each nontrivial divisor, and then the number itself. And finally, the third line calls Divisor to generate all the numbers 16 is divisible by.

So, we've seen how a multi-value expression behaves within square brackets. Let's have a look at how they interact with other expressions. For example:


Yes, writing 10 + (1..6) is like writing 10+1, 10+2, 10+3, 10+4, 10+5, 10+6. Basically, the whole expression 10+(something) is being evaluated once for each (something). If there are two or more multi-values, we run the expression once for each pair of values - each value on the left gets combined with each value on the right:


Functions and string interpolations also work this way. For example, here we call print repeatedly:


There's an important corollary to this: when you define a function, the function parameters it receives are guaranteed to be single values. If someone does call your function with a multivalue, the function body will just run multiple times.

Oh, and there's one function which gets called this way an awful lot. We saw it in the bottles of beer example - it's called for.


The last concept I want to introduce here is called zero values. It follows fairly obviously from the features I just explained: if an expression can evaluate to more than one value, naturally it can also evaluate to less than one. each[5,6,7] returns three values, while each[] returns zero values.

Similarly, if you write print(each[5,6,7]), the print function gets called three times. And if you write print(each[]), the print function doesn't get called at all.

Enter the operator ?. It takes any number of values, and drops any that are false or null. So null? will return zero values, just like each[].


This turns out to be great for aborting a series of function calls that might fail at any point:

ApronPocket: [bob?.mother?.apron?.pocket?];

That is to say: if bob exists, get his mother; if his mother exists, get her apron; if her apron exists, get its pocket. But if any of these is null, don't call any more functions: ApronPocket will just become [].

In fact, ? acts as the language's primitive building block for all conditionals. The if function can be trivially defined using ?:


Ta-daa! In Swym, if is just a normal function. It takes a value and a function, and calls the function if and only if the value is truthy.

Note that if the value is falsy, if returns zero values. This is pretty handy, as it turns out, because we can detect the difference with...


After all, how silly would it look to have an "if" without allowing an "else"?

else is a keyword. (One of a very select set, in fact, because the language only has three keywords - is, else, and etc). On its left hand side, else takes any number of values. If there's one or more of them, it returns those values unchanged. If there are zero values, it evaluates and returns its right hand side instead.

(As is probably becoming clear, Swym is a C-like language that's built out of lego bricks.)

One last thing to note - a? else{b} is a handy way to express a "fallback" in the event that a is null. But there's no easy way to emulate an a?b:c operator this way. You'll just have to say if(a){b}else{c}.


Section 5 - The Etc keyword

I think etc is probably the part of Swym that I'm most proud of. It's the ultimate embodiment of language's design philosophy: code readability, at the cost of all else. Writing etc in an expression is your way of telling the compiler "...and so on in the obvious way. You figure it out". Let's have a look:


There isn't all that much for the compiler to figure out, here... but we can already start to get a hint of how powerful this concept can be. The etc keyword must appear immediately after an infix operator, and it searches the surrounding code for a chain of instances of that operator, and the arguments they take. In this case it sees the + operator, and two arguments: .1st and .2nd. From these it can extrapolate .3rd, .4th and so on, stopping (it helpfully deduces) when the end of the list is reached. Let's see another example:


Now we're making a sequence of , operators, and we have two terms to extrapolate from - [.1st, .2nd] and [.3rd, .4th]. What's the difference between these two expressions? Well, .1st has been replaced with .3rd, and .2nd has been replaced with .4th. The compiler considers each of these differences independently. So for the first case it will deduce the sequence .1st .3rd .5th .7th..., and for the second, .2nd .4th .6th .8th....

And, for those who are wondering: yes, 2-4-6-8 is the default, but you can get it to understand 2-4-8-16 if you give enough examples.


Also note the halt condition, which - of course - is reminiscent of the 1..<10 notation we saw earlier. An etc..< expression will terminate as soon as it sees a term that's greater than the termination value. Note that while the left hand side of an etc expression is handled at compile time, the termination value is determined at runtime. And as you'd expect, there's a related family: etc..<= etc..> etc..>=.

We can use this to decompose a number into powers of two -


The compiler always assumes that each entry in the sequence follows directly on from the previous one. So if you'd like your etc sequence to alternate in some way, you'll need to bracket the expressions. let the compiler deduce how each group of terms follows on from the previous group.


In the end, the compiler processes Interleave in almost exactly the same way as byPairs.

Here's a quick demonstration of how to define your own each function. This is actually how the built-in each function is defined. Look, there's nothing magical about it. (at least, no more magic than the rest of the language!).


And this etc concept keeps on surprising me - every time I work with it, I find something new it can do. Very cool. Here's a particularly elegant one:


And another:


I should point out here that I've capped the number of iterations to avoid having infinite loops in cases like this. At the moment, an etc expression cannot perform more than 1000 iterations. This won't be a permanent limit - just until the compiler can be made a little smarter about detecting loop termination conditions.


Section 6 - Introducing Patterns

The last big concept in Swym that I haven't covered yet is the Pattern. We've seen a few already - here are some of the predefined patterns that Swym offers.

IntAny whole number.
PositiveAny number greater than zero.
NaturalAny whole number greater than or equal to zero.
NumAny number.
PatternAny pattern (function or list).
StringAny string. (a list that contains only characters.)
FunctionAny function.
ListAny list.
BoolThe value true or false.
ValueAny number, list, pattern, function... anything. This is the most general pattern possible.

That's right - the types we've been using are patterns. You can think of a pattern, in general, as a (potentially infinite) set of possible values. The key feature that patterns have is the ability to test whether they contain a given value, using the is operator:


That last line was there to wake you up. A list is a kind of pattern! Specifically, it's a pattern that contains a very limited set of values - the elements of that list. Check out how Bool is defined, for example:


Having said that... what are the more general patterns, like Value or String? Surely they can't be lists of all possible values...?


Well, hey - a function is also a kind of pattern! Specifically, a function is a pattern that contains all values for which it returns a truthy value.

We're seeing something new here: these functions are being stored under ordinary variable names. You may remember, back when I defined myfor, that I said functions have their own namespace - and that if I had called the function f instead of .f, that function would not have been callable. Well, that's what is happening here. UnderTen is a function that has been 'disarmed'. By storing it under a normal variable name, we've made it behave like a normal value - you can pass it around, and test it with is, but it cannot be called unless you first copy it into a .foo variable. (In fact, the easiest way is to call a function that will do that for you: for( x, UnderTen )).

Let's try defining a pattern that's more than just a toy example.


Let's see how many surprises we can pack into this section. Remember the Where function we saw earlier, that filters a list? Well, its argument is actually just a pattern:


As is probably pretty obvious at this point, 'patterns' correspond to sets in set theory. Where is intersection, [] the empty set, and Value the universal set. The operator | serves as set union, and (equivalently) list concatenation. And inversion? Well, that would be the Non function. But hey, why don't we implement our own?


You can even implement Bertrand Russell's favourite paradox if you want:


(Pop quiz: apart from Value, my introductory list contains two other patterns that aren't described by PatternNotContainingItself. Can you name them?)

And naturally, there's no way to answer whether PatternNotContainingItself is PatternNotContainingItself. (In fact, on the current implementation that'll cause a stack overflow).

So: that's the 'what' of patterns, in a nutshell. Now I guess I'd better answer the 'why': what are these things good for? Why are they in the language? Why conflate lists with functions and treat them both as sets?

To start answering that question, here's another example.


The Pair function, applied to a pattern, is a simple enough concept - it lets you confirm that [4,5] is Pair-of-Int, for example.

But, as I played with this concept - and others like it that we'll see in a moment - I realized it ought to be more powerful. There are lots of situations where it's really useful to be able to iterate though every possible value of a given type; and if you use Pair on such a type, it would make sense to allow you iterate through every possible pair of those values.

I soon realized that these 'enumerable types' were essentially the same as lists - and there were plenty of situations where I wanted to use a list function on an enumerable type, or vice versa, so I was having to convert data from one format to the other. Really clunky.

So I unified them. All enumerable types are lists. All lists are enumerable types. And if you call Pair on a list (rather than just a pattern), it returns another list: all possible Pairs of elements of your list.


Internally, however, this thing is smarter than your average list - it doesn't just blindly generate the list of pairs and then try to match potential candidates against every one of the possible pairs. Enumeration happens only on demand; and testing whether a given list is a Pair-of-Bool would use a completely different code-path.

Here are some more functions that produce these "smart lists":

List.SegmentAll the different contiguous segments that can be taken from this list.
List.PrefixAll the segments that start from the first element.
List.SuffixAll the segments that end with the last element.
List.FilterAll the different ways you could drop elements from this list.
List.PermutationAll the different ways to reorder this list.
List.PartitionAll the ways to slice this list into (nonempty) pieces.
P.PairAll the possible lists that contain two P's.
Struct{P1, P2}A generalization of Pair: all the possible lists that contain a P1, then a P2 - or whatever other sequence you specify.

These things are really nice to work with. Who likes anagrams?


(As you may recall, the random function just selects one random element from a list.)

Alternatively, we can generate new nonsense words:


And usual, the next step in this process is to start defining our own! The simplest way to create a lazy-evaluated list is the LazyList function. For example, let's define our own version of the Prefix function - which takes a list, and returns all the ways you could truncate the list.


The LazyList function takes two arguments: the length of the list we're generating, and a function that generates elements of that list, when given their index.

It also has an optional third argument: a pattern that we can use to quickly decide whether a value is in the list. This is just an optimization - if you don't provide one, testing it will still work, but it'll be much slower. It'll search through the list, checking each element to see whether it matches. The whole point of using a LazyList, though, is that it's (potentially) an incredibly long list with a definite pattern to it. Let's try defining that Pair function.



Section 7 - Future Development

This section is just to show off some more features that are more or less working, but I'm not satisfied with their current design.

Quantifiers:


Quantifiers allow you to say what you mean using a convenient, familiar english concept. They're currently implemented as a special kind of multi-value. So you can think of the functions .some and .every (and their less positive brothers .no and .notevery) as being strange cousins to the .each function.

Basically, this expression some(A) > every(B) is getting executed once for every possible pairing of A and B values - but stopping when a match is found. Note also that lexical order matters - so some(A) == every(B) is quite different from every(B) == some(A). (The first one checks whether there's just a single value in A that's equal to all the values in B. The second one checks whether each of the values in B is equal to some value - but not necessarily all the same value - in A).

In an ideal world, I'd like to allow .some (but not the others) to bind the value it has matched: if(/test( result:some(A) )){ ...do something with result... }. I also want to find a way to get rid of the resolve operator, / - that's how you convert these multi-values into single boolean results. But it feels clumsy to me - I want them to be auto-resolved reliably enough that you almost never need to use it. I just haven't found the correct rule for when to do it.

Cells:


Cells are an amazingly useful and natural concept but, again, I'm not 100% happy with them at the moment. Essentially, a cell is an element of a list that has added metadata: it knows what list it's from, and which index it was at within that list.

To get started, the Cell function is used to convert any list into a list of cells. The cells are the same as the elements of the original list, except that they respond to three extra functions: .sourceList to get the original list, .index to get this cell's index within that list, and .cellValue to strip away this metadata and just return the original value.

So far, this probably sounds moderately useful, but a little clunky. Why do we use the Cell function - isn't that just a needless extra step? Why can't this information come along for free, every time we get an element from a list? Well, actually, this explicit call provides one crucial benefit: we make it possible for the programmer to specify exactly which source list he was interested in. In other words, it becomes possible to make a list that contains cells from another list.


Here, the function Where scans through the list we give it, selects the values that are greater than 115, and uses them to create a list Y. But since the input list is Cell(X), Y is actually a list of cells - cells whose source list is list X. Which means that when we tell the Each function to get the index of each of them, we see their index within the list X, not their index in the list Y.

I said that cells behave the same as the value they're based on... but there is one more difference:


When you use a range operator on a pair of cells, you're not getting a numeric range: you're getting a range of cells from the source list. This makes it amazingly easy and natural to manipulate slices of a list, and do all kinds of useful tricks.

Finally, I should point out you're not limited to calling the standard .sourceList and .index functions. If you want, you can define your own functions that operate on cells:


So, yeah, it's all really cool stuff... but the current implementation has problems. If you filter a list of cells (as we saw above), and then you use MyMiddle on the filtered list, the result will have lost its original cell metadata. MyMiddle calls Cell internally, so the cells it returns are always sourced to the filtered list! I haven't entirely figured out what I want to do about this, but I think I want some lexical scoping here. Each block of code can only have one cell list, and if you call .Cell on a list, then that's the cell list you're going to use. Any use of .index - or any other function that takes a Cell as its argument - will implicitly select that cell list.

Vars:


Eagle eyed readers may have noticed that this is the first time I've used an assignment operator in this entire tutorial. Swym is powerful enough that mutable data is often unnecessary, but I'm a pragmatist: when you need it, you need it. So I added support for opt-in mutable data.

The question is, how should it work? Under the current implementation, it's a form of metadata, much like the cells we just saw. As you can see, you create new variables with the .var function. It takes one argument, a pattern: in this case, Int. So this variable can only contain integer values. Initially, we assign it the value 5, which means x can be treated almost exactly like the value 5, but with one extra feature: you can assign it new values with the = operator (and others like += or --). ()

This seems like a pretty nice model. For example, it means you can create a list of variables, generate a new list containing some of those variables, and use that to modify the original list.


In contrast to the other features in this section, I haven't really found a reason to call this "flawed" - it's just an area for future development. Sometimes, Saying What You Mean has to involve mutable data, because that's just What You Mean.

Ok, well... th-th-that's all I have for now, folks! Hope you enjoyed it!