-
Notifications
You must be signed in to change notification settings - Fork 1
Language Overview
This document is a quick overview of Fold for the impatient, covering the language syntax and describing its fundamental programming constructs.
Many small but practical examples will demonstrate how the language feels and works. For a detailed step-by-step introduction to the language read the Language Guide or, if you are looking for a formal specification, consult the Language Reference.
Here is how the classic looks in Fold:
def main () =
print "hello, world"
It is also a complete program that can be compiled and run.
$ cat > hello.fold
#!/usr/bin/env fold
def main() =
print "hello, world"
$ chmod u+x hello.fold
$ ./hello.fold
hello, world
Additionally the REPL (read-eval-print loop) interface can be used for the
interactive experimentation. To start the REPL simply type fold
:
$ fold
▗▐▝ ▐▐ ▐▐ | A modern pragmatic functional language.
▐▐ ▗▗ ▐▐ ▗▗▐▐ |
▝▝▐▐▝▝ ▐▐ ▐▐ ▐▐ ▐▐ ▐▐ | Version 0.0.1-alpha+001 (2015-02-12)
▐▐ ▐▐ ▐▐ ▐▐ ▐▐ ▐▐ | http://fold-lang.org
▐▐ ▝▝ ▝▝ ▝▝▝ | Type \? for help
▗▐▝
"Simplicity is prerequisite for reliability."
— Edsger W. Dijkstra"
->
In the following sections the lines starting with ->
simulate the interactive
input, followed by the inferred type (annotated with ::
operator) and the
computed result.
Both single-line and multi-line comments are supported. Multi-line comments can be nested.
-- This is a line comment.
{- Block comments can span
multiple
lines. -}
In this section boolean values and logical operators are introduced. Logical operators can be applied both to boolean values and to lists with boolean values.
-> True
:: Bool = True
-> False
:: Bool = False
-> True && not (True || False)
:: Bool = False
-> [True, True, False] && [False, True, True]
:: List Bool = [False, True, False]
-> not [False, True, True]
:: List Bool = [True, False, False]
Numeric values with different radices are supported. Underscores in the numeric values can improve readability and are ignored by the compiler.
-> 42
:: Int = 42
-> 4_294_967_296
:: Int = 4294967296
-> 0b0101010
:: Int = 42
-> 0xBADC0DE
:: Int = 195936478
-> 0o377
:: Int = 255
-- The pi constant
-> 3.1415926
:: Float = 3.1415926
-> 10.0 * 2 - 10.0 / 2
:: Float = 15.0
Characters, strings and string interpolation.
'a' :: Char
"hello" :: String
-- String concatentation.
-> "hello" ++ " " ++ "world" ++ "!"
:: String = "hello world!"
"""
Multi-line strings can span multiple lines and
don't require escapes for "quotation marks".
"""
-- The dollar sign is used for string interpolation.
val name = "world"
print "hello, $name!"
-- Complex expressions can also be interpolated as long as they return strings.
print "hello, $(String.capitalize name)!"
Regular expressions have builtin support in the language. Fold's interpreter shows examples of matched strings.
-> /^[a-z0-9_.+-]+@[a-z0-9-]+\.[a-z]+$/
:: Regex = /^[a-z0-9_.+-]+@[a-z0-9-]+\.[a-z]+$/
-- Examples:
-- - "[email protected]"
-- - "[email protected]"
Unit, written as ()
, is a special value for expressions without value.
Theoretically it can be seen as nullary tuple or a type with a singleton value
()
. It is similar to the void
type found in imperative languages. Functions
that perform side-effect, such as print
return ()
.
Global definitions are introduced with the val
keyword.
val a = 42
val b = a + 1
Value bindings are immutable, multiple definitions of the same name shadow the previous definition.
val a = 0
-- The value of b was not changed.
do assert (b = 42)
-- Bindings can be defined in a limited scope using `let`.
-> let
a = 3 * 8,
b = 4 + 2
in
a + b
:: Int = 30
-- The bindings can also follow the expression using `where`.
-> a + b where
a = 3 * 8,
b = 4 + 2
:: Int = 30
References are mutable boxes. To create a reference the &
operator can be used.
The reference can be changed with the :=
assignment operator and the original value
can be dereferenced with the *
unary operator.
-> val a = &42
a :: &Int
-> a := 0
-> val b = a
b :: &Int
-> a := 100
-- References need to be dereferenced.
-> assert (*a == *b == 100)
Special syntax for monadic binding is supported with the do
notation and <-
.
val number :: Int? =
Option.do
let s <- Console.read_line (),
n <- Int.parse n
in
return n
end
The previous code creates a monadic bind chain. In practice the handling of the optional values happens implicitly avoiding the tedious repetitive checks shown here:
val number :: Int? =
match Console.read_line ()
| Some s ->
match Int.parse s
| Some n -> Some n
| None -> None
end
| None -> None
end
Multiple expressions can be grouped into blocks. Blocks evaluate to the value of the last expression and are equivalent to parentheses.
Note: The compiler will issue a warning if a block contains intermediate non-unit values, these should be ignored explicitly.
-> val result = do
let a = 2
let b = 3
in
print ("a = %d, b = %d" % (a, b));
a + b
end
result :: Int = 5
-- The inferred type in this case is the symbol itself but in reality it is a union of all possible symbols.
-- This means that *a* in the following example can be either `ok or `no.
-> val a = if x > 4 then `ok else `no
a :: (`ok | `no)
-- Just say hello.
def hello name =
print "Hello, $name!"
-- Hello function with a type annotation.
def hello (name :: String) -> () =
print "Hello, $name!"
-- Lambda greeting. Equivalent to the previous examples.
hello = name -> print "Hello, $name!"
-- Lambda greeting with a block body.
hello = name -> do
print "Hello, $name!"
end
-- Hello function with separate type annotations.
val hello :: String -> ()
def hello name =
print "Hello, $(String.capitalize name)!";
print "Hello, again"
-- Factorial with implicit pattern matching.
def factorial 0 = 1
| factorial n = n * factorial (n - 1)
-- Create a view for a list.
def next [] = None
| next [x, ...] = Some (x, xs)
-- Derivative
def derivative epsilon: (e = 0.0001) f =
x -> (f (x + e) - f x) / e
def square x = x * x
def around delta target =
actual -> abs (actual - target) < delta
def main () =
let dsquare = derivative square in
assert (dsquare 5 |> satisfies (around 0.1, 10));
assert (dsquare 10 |> satisfies (around 0.1, 20))
-> help
Help function shows helpful comments for modules functions and types.
-> help List.map
List.map :: (a -> b) -> List a -> List b
List.map f [a1, ..., an] applies function f to [a1, ..., an],
and builds the list [f a1, ..., f an] with the results returned by f.
List.map is tail-recursive.
List.map has O(n) complexity.
-- Lists
-> [1, 2, 3, 4, 5]
:: List Int = [1, 2, 3, 4, 5]
-- Sets
-> Set.new [1, 2, 3, 4, 5, 4, 2]
:: Set Int = {1, 2, 3, 4, 5}
-- Vectors
-> Vec.new [1, 2, 3, 4, 5]
:: Vec Int = Vec [1, 2, 3, 4, 5]
-- Arrays
-> Vec.new [1, 2, 3, 4, 5]
:: Vec Int = Vec [1, 2, 3, 4, 5]
-- Ranges
-> 0 .. 9
:: Range Int = 0 .. 9
-- Ranges also work with dates and user-defined types.
-> Date "2016-01-01" .. Date "2016-01-31"
:: Range Date = 2016-01-01 .. 2016-01-31
-- Ranges up to a limit (excluding the limit) can be created with the `^` operator.
-> val r1 = ^4
r1 :: Range Int = 0 .. 3
-- Lists can be created with ranges.
-> List.new (1 .. 5)
:: List Int = [1, 2, 3, 4, 5]
-- Get the first, the nth and the last element of the list.
-> List.first a -- May return None if the list is empty.
:: Int? = Some 0
-> a # 3 -- Equivalent to `List.nth a`
:: Int? = Some 3
-> a # -1 -- Equivalent to `List.last a`
:: Int? = Some 4
-- Finding elements and indices.
-> 2 in? a
:: Bool = True
-- The `in` operator can also be used to test a list of elements.
-- For every element in `^5`, tell if it exists in `[0, 3, 4]`.
-> ^5 in [0, 3, 4]
:: [Bool] = [True, False, False, True, True]
-- Create pairs by zipping two lists.
-> zip (^5) (^5 in [0, 3, 4])
:: List (Int, Bool) =
[(0, True),
(1, False),
(2, False),
(3, True),
(4, True)]
-- Lists support pattern matching.
-> val [head, tail...] = 1 .. 5
head :: Int = 1
tail :: List Int = [2, 3, 4, 5]
-> [1, 2, 3] ++ [4, 5, 6]
:: List Int = [1, 2, 3, 4, 5, 6]
-> [1, 2, 3, 4, 5, 6] \\ [1, 3, 5]
:: List Int = [2, 4, 6]
-- Add indices to all elements in `a`.
-> val a = [10, 20, 30, 9]
-> zip (^ #a) a
:: List (Int, Int) = [
[(0, 10),
(1, 20),
(2, 30),
(3, 9)]
-- Find indices in `a` that exist in `b`.
-> val a = [10, 20, 30, 9]
-> val b = [2, 50, 10, 30]
-> select (a in? b) (^ #a)
:: List Int = [2, 3]
-- Functions can be used to combine collections.
-> zip with: (+) [4, 2, 3] [8, 5, 7]
:: List Int = [12, 7, 10]
-> zip with: (-) [4, 2, 3] [8, 5, 7]
:: List Int = [-4, -3, -4]
--
-- Tuples
--
-- Tuples are heterogeneous fixed-size data containers.
-> (1, 'a', "hello", 1..10)
:: (Int, Char, String, Range Int) = (1, 'a', "hello", 1..10)
-- Tuples can be used to bind multiple values at once. Also parenthesis are optional.
-> val (a, b) = 42, 'λ'
a :: Int = 42
b :: Char = 'λ'
-> val (a, [head, tail...]) = "Свобода", [1, 2, 3, 4]
a :: String = "Свобода"
head :: Int = 1
tail :: List Int = [2, 3, 4]
--
-- Types
--
-- Type annotations can be used anywhere, even in-lined with expressions.
-> "Hello " :: String ++ "World!"
= "Hello World!" :: String
--
-- Records
--
-- A record is a tuple with labeled values.
-> val a = { x = 2, y = 3 }
:: { x :: Int, y :: Int } = { x = 2, y = 3 }
-- The previous record was an example of an anonymous record type.
-- The record can be defined as a type first.
-> type Point = { x :: Int, y :: Int }
-> val p = { x = 2, y = 3 }
:: Point = { x = 2, y = 3 }
-- Record update syntax: change the value of one of the fields.
-> { p with x = p.x + 100 }
--
-- Functions
--
-- Function calls start with a function name and arguments separated with spaces.
-> print "Hello"
"Hello"
-> sum 2 (1 + 1)
:: Int = 4
-- Function application has precedence over operators.
-> sum 1 1 * 2 -- Will be parsed as `(sum 1 1) * 2`.
:: Int = 4
-- Lambda functions are created with `->` operator.
-> n -> n * n
:: Int -> Int
-> (n -> n * n) 2
:: Int = 4
-> def sum = x y -> x + y
:: Int -> Int -> Int
-- A function that computes the average of a list of integers.
-> val avg = xs -> reduce (+) xs / #xs
:: List Int -> Float
-- Implicit lambda function:
-> 2 + \x -- Is equivalent to `x -> 2 + x`
:: Int -> Int
-- Filter even numbers
-> List.filter (\n % 2 == 0) [1, 2, 3, 4, 5, 6]
:: List Int = [2, 4, 6]
--
-- Optionals
--
-- To express a possible absence (and therefore presence) of values, an
-- optional type can be used.
-> Some 42
:: Int? = Some 42
-> None
:: a? = None
-- Optional types end with an interrogation point `a?`.
-- To force an optional value the exclamation point can be used `a!`.
-- Warning: if the value is None an exception will be raised.
-> (Some 42)!
:: Int = 42
-> None!
* NoValue (@32, 0/0): optional has no value
-> List.head
:: [a] -> a?
-> List.head ^10
:: Int? = Some 0
-> (List.head ^10)!
:: Int = 0
-- You can provide a default value in case the optional has no value.
-> 2 + List.head [] ? 0
:: Int = 2
-> List.map
List.map :: (a -> b) -> [a] -> [b]
-> map (1 + \x) (1 .. 5)
-- Pattern matching lambda.
-> val hello = None -> "Hello World" | Some name -> "Hello " ++ name
= hello :: String? -> String
-- String interpolation is supported with `%` operator.
def hello name (lang = `en) =
print "$greeting, $(String.capitalize name)!"
where greeting =
match lang
| `en -> "Hello"
| `pt -> "Olá"
| `ru -> "Привет"
end
hello :: String -> lang: (`en | `pt | `ru)? -> ()
-- Named keyword application.
-> hello "Alice" lang: "pt"
"Olá, Alice!"
-- Regular positional application can also be used if the types match.
-> hello "Alice" `en
"Hello, Alice!"
-- The order of the keyword arguments does not have to be the same as in
-- the definition. The remaining positional arguments will be applied by
-- the provided order.
-> map (hello lang: `pt) ["Bob", "Ana", "Tom"]
:: List String = ["Hello, Bob!", "Hello, Ana!", "Hello, Tom!"]
-> map (hello "Ana" \l) [`pt, `en, `ru]
:: List String = ["Olá, Bob!", "Hello, Ana!", "Привет, Tom!"]
-- Named keyword application with scoped variable.
-> lang = `en
-> hello "Alice" ~lang
"Hello, Alice!"
-- The previous call is equivalent to:
-> hello "Alice" lang: lang
"Hello, Alice!"
-- Function composition and reverse application.
-> 1 .. 10 |> filter even |> reverse |> take 3 |> list
:: List Int = [6, 4, 2]
-> iterate (+ "a") "" |> take 5 |> tail |> filter (s -> length s < 4)
:: List String = ["a", "aa", "aaa"]
-- Operators are functions.
-> (+)
:: {Num a} -> a -> a -> a
-> help (+)
:: {Num a} -> a -> a -> a
Produces a sum of two numerical values.
The parameters of the sum function must implement the Number interface.
-- Sum all numbers in a list:
-> reduce (+) [1 .. 5]
= 15 :: Int
-- Finds the maximum number in a list.
-> val max list = reduce (a b -> if a > b then a else b) list
max :: List Int -> Int
-> max [2, 10, 3, 6, 9, 9]
:: Int = 10
-- `for` macro iterates on a collection without producing values.
-> for i <- (1 .. 3)
print ("Number: " ++ (i + 2)::String)
end
Number: 3
Number: 4
Number: 5
val last :: List a -> a?
def last [] = None
| last [x] = Some x
| last [x, xs...] = last xs
-- Some HTML
-> div class: "menu-container" do
ul id: "menu" class: "main-menu" do
map (li class: "menu-item") ["Home" "Products" "Contacts"]
end
end
--
-- Modules
--
type Ordering = LT | EQ | GT
interface Comparable a
val compare :: a -> a -> Ordering
end
instance Comparable Int
def compare x y =
when
| x == y -> EQ
| x > y -> GT
| x < y -> LT
end
end
-- Function definition with docstring.
val quicksort :: {Ord a} -> List a -> List a
def quicksort [] = []
| quicksort [x, xs...] = head ++ [x] ++ tail
where head = quicksort (filter (e -> e < x) xs),
tail = quicksort (filter (e -> not (e < x)) xs)