idk-lang 2

Let's see some code!

The code samples I've provided here are subject to not be valid in any future implementation of this language. I'm still trying to figure everything out, and it's hard to go back and change everywhere where I stated something about this language. So, I don't think things will change much, but they will be left here to preserve a record of progress, so I can see what this language really started out as.

Literals

Here's what the literals look like:

1                    # Num
2.0                  # Fractional
3e4                  # Fractional
5i                   # Complex
True                 # Bool
False                # Bool
(1, True, "a")       # (Num, Bool, String)
[1, 2, 3]            # [Num] or List<Num>
{1, 2, 3}            # {Num} or Set<Num>
{"a": 1 "b": 2}      # {String: Num} or Dict<String, Num>
"Hello, World!"      # String

Notice the types on the right of each literal expression. Num, Complex, and Fractional are all traits. The type of these literals is similar to Haskell's typeclasses of the same name. They represent the most general type that this literal can be. We'll talk about traits in more detail in a later section. Bool is a type with two constructors that take no arguments, True and False. Lists and Sets are heterogenous, that is, they only hold objects of the same type. Lastly, String is not a trait like Num and friends, it is a proper class. It can be instantiated, and cannot be inherited from, unlike traits. We'll talk about classes in more detail in a later section.

Binary Operators

Here are some basic operators:

1 + 1     # 2
1 - 1     # 0
2 * 2     # 4
1 / 2     # 0.5
3 ^ 2     # 9

Even though we haven't talked about idk-lang's type language yet, we will use it here for a quick showcase of some of the features of this language. The type of + is Addable a => a -> a -> a. That is, + takes in two objects of the same type a where the type a must inherit the trait Addable. Since Num and String both inherit Addable, the following expressions are both valid,

10213674 + 7868297       # 18081971
"Hello, " + "World!"     # "Hello, World!"

This shows one of the many advantages to using traits in a statically typed environment. It prevents the creation of over-engineered syntax to handle the “overloading” of operators on different types.

Boolean Operators

Lastly, we have the boolean operators:

not True         # False
True and False   # False
True or False    # True

The operator not has type Bool -> Bool. The operators and and or both have type Bool -> Bool -> Bool. These work as expected, and because of how strict this language is, there is never an implicit cast or coercion to any type, so these functions only work on booleans.

Comparison Operators

We've seen some basic binary operators: + - / * ^. A valid type for all of these operators is Num a => a -> a -> a. That is, they take in two objects of the same type a that implement the trait Num, and return an object of type a that also implements that Num. These are called Numeric operators.

There is another group of operators called Comparison operators. They all have type a -> a -> Bool. That is, they take in two objects, both of type a and the and they return a Bool. Now, a are type variables, they represent any possible type. For example, the following operators are Comparison operators

1 = 1     # True (structural equality)
1 < 2     # True
1 > 2     # False
1 /= 1    # False

What is special about these operators is that they can be chained for greater expressive power. For example, this is a valid use of these operators,

1 < 2 < 3      # True
1 < 2 > 3      # False
1 = 1 < 3      # True

These chains are equivalent to checking that every adjacent pair of arguments to each operator returns True. In the case of function calls inside chains, they are only executed once per chain.

Partial(ly applied) operators

Another cool feature of operators is that they can be partially applied, esentially equivalent to Haskell's operator sections except without any iterated section support. For example,

(<0)   # A function that returns True if the input is less than 0
(=1)   # A function that returns True if the input is equal to 1
(2*)   # A function that multiplies its input by 2

These can be really expressive when using maps or filters.

The next section is on forms, where we'll talk about assignment, if expressions, and where expressions.