DocsLanguage
Syntax and operators.
Read values, expressions, blocks, and the operators used in everyday Fango.
A Fango program is built out of expressions: small pieces that each produce a value. We’ll start with plain values and function calls, then see how conditionals and blocks combine them. Every complete example below runs in the playground, so feel free to try them as you read.
Values and operators.
main() =
print (6 * 7)
print (1.5 + 2.0)
print ("hello" ++ " world")
print (3 < 5)
This prints 42, 3.5, hello world, and True. Integer literals can be used at any numeric type, while decimal literals are Float values. Strings use double quotes, characters use single quotes, booleans are True and False, and Unit is ().
Parentheses make grouping explicit, and you’ll use them often. Function application binds more tightly than infix operators, so to print a sum you write print (1 + 2). The arithmetic operators come from ordinary type classes. Concatenation is ++, inequality is /=, and the boolean operators are && and ||.
Most of the time, though, you’ll build text with interpolation:
main() =
name = "Ada"
count = 3
print "#{name} has #{count} items."
print "Doubled: #{count * 2}. Shown: #{show name}."
This prints Ada has 3 items. and Doubled: 6. Shown: "Ada". Each #{...} holds an ordinary expression from the surrounding scope. Its value is rendered with Display, the same text print writes, so a string appears as its contents; use show when you want the quoted form. The holes run once each, from left to right, so any effects inside them happen in that order. An interpolated string, holes included, must fit on one line. To write the characters #{ literally, escape them as \#{. Character literals, regular expressions, and strings in patterns don’t interpolate.
Lists look like [1, 2, 3] and pairs like (1, "one"). Types and patterns shows how to take them apart again.
Blocks and sequencing.
main() =
price = 12
quantity = 3
total = price * quantity
print total
A block runs from top to bottom and returns its last expression. Each binding gives a name to a value, and that name can’t be reassigned. Any expression before the last one has to return Unit, which is why output operations can come first. When you want a call’s effects but not its answer, bind the answer to _, as in _ = Async.send channel message. Indentation defines the block, using spaces rather than tabs, and a semicolon lets you put several items on one line.
A line comment starts with --. A block comment is written {- ... -} and can contain other block comments, so you can comment out code that already has comments. The {-# ... #-} spelling is reserved for compiler pragmas.
Conditionals.
absolute : Int -> Int
absolute number = if number < 0 then -number else number
main() = print (absolute (-7))
This prints 7. In Fango if is an expression, so it always needs both a then and an else, and both branches must produce the same type. Only the branch that’s selected runs.
When you’d rather choose by the shape of a value than by a yes-or-no test, use case. It’s covered in pattern matching.
Pass a block as a callback.
Braces can introduce a function:
import List
main() = print (List.map { number -> number * 2 } [1, 2, 3])
This prints [2, 4, 6]. { number -> ... } is a lambda, which is just a function without a declared name. A bare { expression } is a callback that takes Unit, handy when a library needs to run some work later. An empty {} is the Unit callback that does nothing, the same as { () -> () }.
Braces also show up in records, which can be confusing at first. Point { x = 1, y = 2 } describes data, while { x -> x + 1 } describes a function. The arrow is the giveaway.
Use items.
A use item hands the rest of a block to a function as a callback:
import Bytes
import Reader
prefix contents =
use reader <- Reader.withBytes contents
Reader.readUpTo reader 4
main() = print (Bytes.toString (prefix (Bytes.fromString "Fango")))
Reader.withBytes calls the rest of the block with reader bound, and that reader can’t escape its scope. use is simply a convenient way to write callback-based APIs without nesting them. It doesn’t add a separate resource-management mechanism.
Declaring operators.
Operators are functions whose names are punctuation. When you introduce one, you declare its fixity, which is its precedence and associativity, and that controls how expressions group. A named function is usually the clearer choice, so reach for an operator only when it makes a repeated pattern easier to read. The pipes |> and <| are already provided; Functions shows how they’re used.
Regular expression literals.
A literal such as /[a-z]+/ is a compiled regular expression, and the compiler checks the pattern for you. To match it against text, use Regex. The regex engine doesn’t support lookaround or backreferences.
