Chapter 3: Syntax and data-types
Where you will learn the what Lux code is made of.
Syntax for data-types
Bit
s look like this:
#0 ... false
#1 ... true
Nat
s look like this:
0
123
0456
123,456,789
Int
s look like this:
+0
-123
+0456
-123,456,789
Rev
s look like this:
.123
.04,56
.7890
Frac
s look like this:
+123.456
-456.7890
+0.001
123,456.789
Text
s look like this:
"This is a single-line text"
Unit
looks like this:
[]
- Variants look like this:
{#Foo}
{#Bar 10 +20.0 "thirty"}
- Tuples look like this:
[123 ["nested" #tuple] true]
- Records look like this:
[#name "Lux" #paradigm {#Functional} #platforms (list {#JVM})]
Note: As you can see, commas (,
) can be used as separators for the numeric literals.
Regarding those label thingies we saw earlier, they're called labels, and the reason they're not mentioned as part of Lux's data-types is that they're not really data-types; they're just part of the language syntax.
They're used as part of the syntax for data-types, but they're not data-types in themselves.
Also, you can't just use anything you want as a label, as you first have to declare them.
We'll talk more about labels a bit later, when we talk about defining types.
Also, just from looking at the syntax for unit and tuples, you can see that they look quite similar. The reason is that unit is actually the empty tuple. I know it sounds odd, but for the most part you just have to think of unit as a kind of empty value, considering that it contains no information inside.
It might sound specially odd that we have an "empty" value at all in the first place, but as it turns out, it's quite useful in a variety of situations.
You're about to see one of those pretty soon.
In the section for variants, you can see 2 different alternatives, and you might wonder how do they differ.
Well, a variant is a pair of a label (referred to as a tag) and a single value.
That's right, I said single value; so you might be wondering how come we're associating 3 values with the #Bar
tag.
It's pretty simple, actually: whenever you're trying to create a variant with more than one value, Lux just wraps all the values inside a tuple for you.
So, {#Bar 10 +20.0 "thirty"}
is the same as {#Bar [10 +20.0 "thirty"]}
.
Now, you might be thinking: what's up with that #Foo
variant?
Well, sometimes you only care about a variant for its tag, and not for any value it may hold (for example, if you're trying to use a variant type as an enumeration).
In that case, you'll want to pair the tag with an empty value (since it has to be paired with something).
That's right! You've just witnessed unit value in action and you didn't even know it.
If you just write the tag in braces, with nothing else in there, Lux will stick a unit in there for you.
That means {#Foo}
is the same as {#Foo []}
.
You might have noticed that I mentioned records in this chapter, but not in the previous chapter, where I also talked about the basic data-types Lux offers.
The reason is that records are a bit of a magic trick in Lux.
That means records are not really a data-type that's distinct from the other ones.
In fact, records just offer you an alternative syntax for writing tuples.
That's right! [#name "Lux" #paradigm {#Functional} #platforms (list {#JVM})]
could mean the same as ["Lux" {#Functional} (list {#JVM})]
, depending on the ordering imposed by the slots (which is the name given to record labels).
Remember when I said that you needed to declare your labels?
Well, depending on the order in which you declare them, that means that #name
could point to the first element in the tuple, or to another position entirely.
Also, in the same way that labels have a numeric value when it comes to their usage in tuples/records, that's also the case for variants.
For example, the List
type has two tags: .#End
and .#Item
.
The .#End
tag has value 0, while the .#Item
tag has value 1.
That's what allows Lux to the able to identify which option it is working with at runtime when you're dealing with variants.
Labels belong to the module in which they were declared, and you must use the module name (or an alias) as a prefix when using tags.
That is why I've written .#End
and .#Item
, instead of #End
and #Item
.
However, you may forgo the prefixes if you're referring to tags which were defined in the same module in which they're being used.
Types for data-types
"But, wait!", you might say. "We didn't talk about functions!"
Patience, young grasshopper. We'll talk about those in the next chapter.
For now, let's talk about types.
The type-annotation macro is called is
. You use it like this: (is Some_Type some_value)
.
There is also a separate macro for type-coerciones that's called as
, which is used the same way. However, you should probably steer clear off that one, unless you know what you're doing, since you can trick the compiler into thinking a value belongs to any type you want by using it.
Now that we know about type annotations, I'll show you some types by giving you some valid Lux expressions:
(is Bit #1)
(is Bit .true)
(is Nat 123)
(is Int -123)
(is Rev .789)
(is Frac +456.789)
(is Text "YOLO")
(type Some_Enum
(Variant
{#Primitive}
{#Variant}
{#Tuple}))
(is [Int [Text Some_Enum] Bit]
[10 ["nested" {#Tuple}] .false])
(type Quux
(Variant
{#Foo}
{#Bar Int Frac Text}))
(is Quux {#Foo})
(is Quux {#Bar 10 +20.0 "thirty"})
(type Lang
(Record
[#name Text
#paradigm Paradigm
#platforms (List Platform)]))
(is Lang
[#name "Lux"
#paradigm {#Functional}
#platforms (list {#JVM})])
(is Lang
["Lux" {#Functional} (list {#JVM})])
(is [Text Paradigm (List Platform)]
[#name "Lux"
#paradigm {#Functional}
#platforms (list {#JVM})])
By the way, the value of a type-annotation or a type-coearcion expression is just the value being annotated/coerced. So `(is Bit #1)` simply yields `#1`.
What is that type
thingie?
It's just a macro for defining types. We'll learn more about it in a future chapter.
The labels that get mentioned in the type definition get automatically declared, and the order in which they appear determines their value.
#Foo
came first, so its value is 0. #Bar
, as you may guess, gets the value 1.
Also, you might be wondering what's the difference between List
and list
. Well, the first one is the type of lists (or a type-constructor for list types, however you want to look at it). The second one is a macro for constructing actual list values. List
can only take one argument (the type of the list elements), while list
can take any number of arguments (the elements that make up the list value).
Again, we haven't mentioned functions. But the next chapter is about them.
See you in the next chapter!