Getting started, 3 of 4
A tour of the language
Build a small expense report and meet the language's main parts on the way.
What you’ll build
A program that reads lines of expenses, adds them up by category, and reports the lines it couldn’t read. It’s built in five steps, and each one adds a part of the language: functions, your own types, collections, null safety, and errors. Each part is taught on its own later in Learn; this tour shows how they fit together.
Values and functions
A function, declared with Function, takes parameters, each with its type, and its return type
follows a colon. A function that is one expression can be written after =>. This one turns a number of cents into
dollars, using a format spec, :02, which pads the cents to two digits.
Import std.core
Function Dollars(cents: Int): String => "${cents / 100}.{cents % 100:02}"
Local rent = 90000
Local food = 1250 + 875
Print("rent {Dollars(rent)}, food {Dollars(food)}")rent $900.00, food $21.25
cents / 100 is integer division, since both sides are Ints, and % is the remainder.
A type of your own
A Struct groups fields into one value. It gets a constructor that takes its fields in order,
and an array literal, in brackets, holds several of them. For ... In goes over an array.
Import std.core
Struct Expense
Field Category: String
Field Cents: Int
End Struct
Function Dollars(cents: Int): String => "${cents / 100}.{cents % 100:02}"
Local expenses = [Expense("food", 1250), Expense("rent", 90000), Expense("food", 875)]
For expense In expenses
Print("{expense.Category:<6} {Dollars(expense.Cents):>9}")
End Forfood $12.50 rent $900.00 food $8.75
:<6 lines a value up on the left of six columns, and :>9 on the right of nine, so the
columns line up.
Totals in a Map
A Map from std.collections holds a value for each key. Made with a default, it gives that
default for a key it doesn’t have, so += can add to a total that starts at 0. A For over a
Map gives each key with its value, in the order the keys were added.
Import std.core
Import std.collections
Struct Expense
Field Category: String
Field Cents: Int
End Struct
Function Dollars(cents: Int): String => "${cents / 100}.{cents % 100:02}"
Local expenses = [Expense("food", 1250), Expense("rent", 90000), Expense("food", 875)]
Local totals = Map<String, Int>(default: 0)
For expense In expenses
totals[expense.Category] += expense.Cents
End For
For category, cents In totals
Print("{category:<6} {Dollars(cents):>9}")
End Forfood $21.25 rent $900.00
Something that might be missing
A type with ? after it, like Expense?, can hold Null, and Expense itself can’t. An array’s
Find gives an Expense?, since nothing may match. Its test is a lambda, a function written
inline with Function. ?. reads a field only when there’s a value, and ?? gives a fallback
when there isn’t, so the program can’t read a field of nothing. The report now ends by naming the
first large expense.
Import std.core
Import std.collections
Struct Expense
Field Category: String
Field Cents: Int
End Struct
Function Dollars(cents: Int): String => "${cents / 100}.{cents % 100:02}"
Local expenses = [Expense("food", 1250), Expense("rent", 90000), Expense("food", 875)]
Local totals = Map<String, Int>(default: 0)
For expense In expenses
totals[expense.Category] += expense.Cents
End For
For category, cents In totals
Print("{category:<6} {Dollars(cents):>9}")
End For
Local big = expenses.Find(Function(e) => e.Cents > 50000)
Print("over $500: {big?.Category ?? "none"}")
Local huge = expenses.Find(Function(e) => e.Cents > 500000)
Print("over $5000: {huge?.Category ?? "none"}")food $21.25 rent $900.00 over $500: rent over $5000: none
Errors
The expenses now come from lines of text, and a line can be wrong. ParseExpense says so with
Throws, and throws an error of its own Class, which extends std.core’s Error. A Class is a type
whose values are objects, shared by reference rather than copied as a Struct is. A call to a
function that throws must handle its errors: here, a Try block’s Catch reports the bad line,
and the loop goes on to the next.
Import std.core
Import std.collections
Import std.text
Struct Expense
Field Category: String
Field Cents: Int
End Struct
Class BadLine Extends Error
End Class
Function ParseExpense(line: String): Expense Throws BadLine
Local parts = line.Split(",")
If parts.Count != 2 Then Throw BadLine("expected 'category, cents', found '{line}'")
Local cents = Int.Parse(parts[1].Trim())
If cents == Null Then Throw BadLine("'{parts[1].Trim()}' isn't a number of cents")
Return Expense(parts[0].Trim(), cents)
End Function
Function Dollars(cents: Int): String => "${cents / 100}.{cents % 100:02}"
Local lines = ["food, 1250", "rent, 90000", "food, 875", "books 2000", "fun, lots"]
Local expenses: Expense[] = []
For line In lines
Try
expenses.Add(ParseExpense(line))
Catch e As BadLine
Print("skipped: {e.Message}")
End Try
End For
Local totals = Map<String, Int>(default: 0)
For expense In expenses
totals[expense.Category] += expense.Cents
End For
For category, cents In totals
Print("{category:<6} {Dollars(cents):>9}")
End For
Local big = expenses.Find(Function(e) => e.Cents > 50000)
Print("over $500: {big?.Category ?? "none"}")skipped: expected 'category, cents', found 'books 2000' skipped: 'lots' isn't a number of cents food $21.25 rent $900.00 over $500: rent
Int.Parse gives an Int?, Null for text that isn’t a number. After the line that throws when
cents is Null, the compiler knows it holds an Int, so Expense(...) takes it as one.
Import std.text brings in String’s Methods, functions called on a String with a dot, like
Split and Trim.
What you learned
- A function declares its parameters’ types and its return type;
=>writes one expression. - A
Structis a value made of fields, with a constructor that takes them in order. - Arrays and std.collections’
Maphold many values, andFor ... Ingoes over them. - A type with
?after it can holdNull, and?.and??deal with it safely. - A function that can fail says
Throws, and its caller handles the error withTry.
The rules are in the language reference.
Report an issue with this page or with FutureBlitz on GitHub.