Strings & Numbers
Before taking a stroll in the diverse garden of Par’s types, let’s stop by the most basic ones: the primitives.
At the moment, Par has four primitive types:
Int
— Integers, positive and negative whole numbers, arbitrary size.Nat
— Natural numbers, starting from zero, arbitrary size. They are a subtype ofInt
.String
— UTF-8 encoded sequencee of Unicode characters.Char
— Singular Unicode characters.
There’s a significant distinction between primitives and all other types in Par.
The thing is, Par has a fully structural type system. All custom type definitions are just aliases — there is no way to create opaque types. (But, encapsulation is perfectly possible.)
Primitives are different in that they are opaque. They are magical types, distinct from others, that are operated on using magical built-in functions. This is necessary to achieve their efficient representation.
Primitives are manipulated using magical built-in functions.
To find the list of all built-in functions:
- Open the playground.
$ par-lang playground
- Press Compile, and Run. Scroll the list that pops up.
To figure out the type of a built-in function:
- Assigning it to your own
def
, such as:
Par knows the type ofdef Examine = Int.ToString
Int.String
, so it will infer it forExamine
as well. - Press Compile.
- Move the cursor the the definition. The playground will display the type on the right,
in green.
The type [Int] String
is a function from Int
to String
. We will cover
functions and other types in detail later. Despite that, we’ll still play with
some built-in functions in this section. All you need to know is that
the square brackets enclose function arguments, and the result type follows. For example:
[Int, Int] Int
is a function from twoInt
s to anInt
.[Int, Nat, Nat] Nat
is a function from oneInt
and twoNat
s to aNat
.
The current set of built-in functions is very minimal. They’re just enough to be able to write more useful functions yourself, but they’re nowhere close to a standard library. For example, there are no functions for analyzing strings, aside from
String.Reader
, which is flexible enough to implement all you’d need. You just need to do it yourself.Keep in mind that Par is early in development. It’s bringing an innovative paradigm, which we’re still figuring out how to use best. Creating an expansive standard library would be premature before we understand what’s actually going on here.
Now, let’s take a look at the primitives!
Int
Integers are arbitrarily sized whole numbers, positive or negative.
Their literals consist of digits, optionally prefixed with -
or +
, and may include underscores
for readability.
def Num1: Int = 7
def Num2: Int = -123_456_789
The type annotations are not needed:
def Num3 = 42
def Num4 = -2202
Without annotations, Num3
actually gets inferred as Nat
. But, since Nat
is a
subtype of Int
, it can be treated as an Int
too.
Built-in functions are used for arithmetic operations. For example:
def Num5 = Int.Add(3, 4) // = 7
def Num6 = Int.Mul(3, 4) // = 12
Go ahead and explore more of them in the playground!
Nat
Natural numbers are just integers excluding the negative ones. Nat
is a subtype of Int
, so
every variable of type Nat
can be used as an Int
, too.
def Num7 = 14 // inferred as `Nat`
def Num8 = 17 // inferred as `Nat`
// perfectly valid
def Num9 = Int.Add(Num7, Num8)
While Num7
and Num8
are inferred as Nat
s, Num9
will be an Int
because that’s what
Int.Add
returns. To get a Nat
result, use Nat.Add
, which only accepts Nat
s:
def Num10 = Nat.Add(Num7, Num8) // inferred as `Nat`
Several built-in functions aid in converting Int
s to Nat
s. For example:
Nat.Max
has type[Nat, Int] Nat
— the second argument is allowed to be anInt
. Yet it’s guaranteed to return aNat
.Int.Abs
has type[Int] Nat
— an absolute value is always aNat
.
def Num11: Nat = Nat.Max(0, -1000) // = 0
def Num12: Nat = Int.Abs(-1000) // = 1000
Unlike Int
s, natural numbers can be looped on using Nat.Repeat
, which is one of their main
uses. We’ll learn more about that in the section on recursive types.
String
Strings are represented as UTF-8 encoded sequences of Unicode characters. Their literals are
enclosed in double quotes ("
), and may contain escape sequences, such as \n
, familiar from
other languages.
def Str1 = "Hello" // inferred as `String`
def Str2 = "World"
To concatenate strings, use String.Builder
. To fully understand how it works, we’ll need to
cover iterative and choice types, but perhaps
you can get the idea:
def Str3 = String.Builder
.add(Str1)
.add(", ")
.add(Str2)
.build // = "Hello, World"
Analyzing strings — such as finding, splitting, or parsing — is done using String.Reader
.
To be able to use it, more knowledge of the language is needed first. But, feel
free to play with it in the playground, or check out the StringManipulation.par
example in
the examples/
folder.
Numbers can be converted to strings using Int.ToString
:
def Str4 = Int.ToString(14) // = "14"
def Str5 = Int.ToString(-7) // = "-7"
Note, that Nat
is a subtype of Int
, so any natural number can also be converted to a string
this way, too. In fact, that’s exactly what happens with Str4
.
Char
A Char
is a single Unicode character. Char
literals are enclosed in single quotes:
def Char1 = 'a' // inferred as `Char`
def Char2 = '\n'
There’s a built-in function to check if a Char
is a part of a character class:
def IsWhitespace = Char.Is(' ', .whitespace!) // = .true!
There’s no built-in function turning a String
to a list of Char
s. Feel free to
copy-paste this one, if you ever need it:
dec Chars : [String] List<Char>
def Chars = [s] String.Reader(s).begin.char.case {
.end! => .end!,
.char(c) rest => .item(c) rest.loop,
}