Skip to content
FutureBlitz

    Install

    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.

    expenses.fb
    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)}")
    Output
    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.

    expenses.fb
    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 For
    Output
    food      $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.

    expenses.fb
    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
    Output
    food      $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.

    expenses.fb
    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"}")
    Output
    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.

    expenses.fb
    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"}")
    Output
    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 Struct is a value made of fields, with a constructor that takes them in order.
    • Arrays and std.collections’ Map hold many values, and For ... In goes over them.
    • A type with ? after it can hold Null, and ?. and ?? deal with it safely.
    • A function that can fail says Throws, and its caller handles the error with Try.

    Report an issue with this page or with FutureBlitz on GitHub.