Chapter 3: Syntax and Data-Types

Where you will learn the what Lux code is made of.


Syntax for data-types

  • Bools look like this:

    true false

  • Nats look like this:

    +10 +0 +20 +123_456_789

  • Ints look like this:

    10 0 -20 123_456_789

  • Reals look like this:

    123.456 -456.789 0.001 123_456.789

  • Fracs look like this:

    .456 .789 .001

  • Chars look like this:

    #"a" #"\n" #"\u1234"

  • Texts look like this:

    "This is a single-line text"

    "And this one is multi-line.
    
     Mind-you, that columns must align on each start of a line, or the compiler will complain.
    
     But empty lines can just stay empty, so you don't need to pad them with white-space."
    
  • Unit looks like this:

    []

  • Tuples look like this:

    [10 ["nested" #tuple] true]

  • Variants look like this:

    #Foo (#Bar 10 20.0 "thirty")

  • Records look like this: {#name "Lux" #paradigm #Functional #platforms (list #JVM)}

As you can see, underscores (_) can be used as separators for the numeric literals.


From looking at this, we can see a few interesting bits we haven't discussed.

One is that the hash (#) character is overloaded.

In the last chapter we saw it being used for comments, but now we're seeing it being used as a prefix for characters, but also as a prefix for some weird "label" thingies (more on that in a moment).

To avoid reserving many characters for the language, Lux overloads the hash (#) character in situations where it can be used unambiguously. That way, most characters can be used by anyone without fear of stepping on the feet of the language.


Regarding those label thingies we saw earlier, they're called tags, 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 tag, as you first have to declare them.

We'll talk more about tags 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 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 in action and you didn't even know it. If you just write the name of the tag without any parentheses, 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 tags.


Remember when I said that you needed to declare your tags? 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 tags 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: #;Nil and #;Cons. The #;Nil tag has value 0, while the #;Cons tag has value 1. That's what allows Lux to the able to identify which option it's working with at runtime when you're dealing with variants.

Tags 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 #;Nil and #;Cons, instead of #Nil and #Cons. 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.


Finally, you may have noticed that, unlike all other data-types, variants re-use some syntax that you're already seen before: the parentheses. Clearly, we didn't build our program by creating a bunch of variants, so, what's going on?

Well, the parenthesis delimit the syntax of what is called a form in Lux. This is actually an old concept that's very familiar to those with experience with other Lisp-like languages. Basically, a form is a composite expression or statement.

When the form starts with a tag, Lux interprets that to be a variant.

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 : (I know, real cute). You use it like this (: Some-Type some-value).

There is also a separate macro for type-coerciones that's called :!, 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:

  • (: Bool true)
  • (: Nat +123)
  • (: Int 123)
  • (: Real 456.789)
  • (: Frac .789)
  • (: Char #"a")
  • (: Text "YOLO")
  • (type: Some-Enum #primitive #tuple #variant)

    (: [Int [Text Some-Enum] Bool] [10 ["nested" #tuple] true])

  • (type: Quux #Foo (#Bar Int Real Text))

    (: Quux #Foo)

    (: Quux (#Bar 10 20.0 "thirty"))

  • (type: Lang {#name Text #paradigm Paradigm #platforms (List Platform)})

    (: Lang {#name "Lux" #paradigm #Functional #platforms (list #JVM)})

    (: Lang ["Lux" #Functional (list #JVM)])

    (: [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 (: Bool true) simply yields true.

What is that type: thingie?

It's just a macro for defining types. We'll learn more about it in a future chapter.

The tags that get mentioned in the type definition get automatically declared, and the order in which they appear determines their value. #Foo came first, so it's 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 lists, 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 element values). list can take any number of arguments (the elements that make up the list).


Again, we haven't mentioned functions. But if you're impatient to learn about them, just turn the page (or scroll down) to find out how to make them!

See you in the next chapter!

results matching ""

    No results matching ""