Martin Sulzmann
package main
import "fmt"
var x int
func hi(y int) {
fmt.Printf("hi %d\n",y)
}
func main() {
x= 1
hi(x)
fmt.Printf("hello, world\n")
}var varName varTypevarName of Type varTypePrintf similar to printf in C, see here for brief description of the C printf variant.
go run hello.go
gofmt hello.go
gofmt -w hello.goChoose your editor (emacs, …)
In most cases, our programs consist of a single file.
The type of y is inferred by the right-hand side.
Pretty convenient!
Languages like Haskell support full type inference where the types of functions can be inferred.
The only control structure.
Infinite loop with break
Unlike in C, Go strings are not null terminated. In Go, strings are represented by a struct consisting of
The length of the string.
A pointer to the first byte.
These implementation details are hidden from the user. The user can
make use of array notation to access a specific position in the string.
There is a primitive len that computes the length of a
string. Strings are immutable. This means they cannot be updated (but
copied of course).
Next, we discuss a few examples that involve strings.
Printing a string.
Several ways to iterate over a string.
for i := 0; i < len(x); i++ {
fmt.Printf("%c", x[i])
}
for i, e := range x {
fmt.Printf("%c", e)
fmt.Printf("%c", x[i])
}
// Don't care notation _
for i, _ := range x {
fmt.Printf("%c", x[i])
}
for _, e := range x {
fmt.Printf("%c", e)
}Via range we obtain current position and element. The
notation _ indicates that we don’t care about the
value.
Out of bounds check.
Short-hand array initialization
Iteration
More flexible arrays (length may change)
Short-hand
Functions on slices, e.g. append
Go uses call-by-value for paramter passing.
Call-by-reference can be emulated via pointers. Go supports pointers (but does not support pointer arithemtic).
Slices are internally represented via pointers.
package main
import "fmt"
func inc(i int) int { return i + 1 }
func mapInc(xs []int) []int {
for i, e := range xs {
xs[i] = inc(e)
}
return xs
}
func mapInc2(xs []int) {
for i, e := range xs {
xs[i] = inc(e)
}
}
func main() {
xs := []int{1, 2, 3}
ys := mapInc(xs)
fmt.Printf("%d %d\n", xs[0], ys[0])
mapInc2(ys)
fmt.Printf("%d \n", xs[0])
}Yields
2 2
3
package main
import "fmt"
func inc(i int) int { return i + 1 }
// Direct reference to return values by name
// Only applies to tuple return values
func myDiv(x int, y int) (res int, status bool) {
status = false
if y == 0 {
return
}
res = x / y
status = true
return
}
func myDiv2(x int, y int) (int, bool) {
if y == 0 {
return 0, false
}
return x / y, true
}
func main() {
var res int
var status bool
res, status = myDiv(inc(3), 2)
fmt.Printf("Result = %d \n", res)
fmt.Printf("Status = %t \n", status)
res, status = myDiv2(1, 0)
fmt.Printf("Result = %d \n", res)
fmt.Printf("Status = %t \n", status)
}We write a function to split a string into a prefix and a suffix once we see given byte.
func splitAt(x string, b byte) (string, string) {
var r1, r2 []byte
suffix := false
for i, _ := range x {
if !suffix {
if x[i] == b {
suffix = true
} else {
r1 = append(r1, x[i])
}
} else {
r2 = append(r2, x[i])
}
}
s1 := (string)(r1)
s2 := (string)(r2)
return s1, s2
}We use slices r1 and r2 to collect the prefix and suffix. We check if we have seen the given element b yet, and then append elements in the original string to either r1 or 2. The element b is not added.
We require slices as append only operates on slices. As we wish to retrieve the result as strings, we need to include a type conversion.
Function splitAt returns two results, the prefix and the suffix. Multiple return results can be retrieved as follows.
Simple (increment) function.
Functions can be arguments.
func apply(i int, f func(int) int) int {
return f(i)
}
func mapInt(f func(int) int, xs []int) []int {
ys := make([]int, len(xs))
for i, e := range xs {
ys[i] = f(e)
}
return ys
}
func execute(f func()) {
f()
}Point to note:
func apply ... declares that we define a function
with name apply
f func(int) int declares that parameter
f is of function type
func(int) int is a function type where the input is
an int and the output is an int as
well
Functions can be return values.
func hello() {
fmt.Print("Hello \n")
}
func hello2(x int) func() {
fmt.Printf("%d ", x)
return hello
}package main
import "fmt"
func inc(i int) int { return i + 1 }
func square(i int) int { return i * i }
func apply(i int, f func(int) int) int {
return f(i)
}
func execute(f func()) {
f()
}
func hello() {
fmt.Print("Hello \n")
}
func hello2(x int) func() {
fmt.Printf("%d ", x)
return hello
}
// Create a new slice.
func mapInt(f func(int) int, xs []int) []int {
ys := make([]int, len(xs))
for i, e := range xs {
ys[i] = f(e)
}
return ys
}
// In-place update. Result refers to xs.
func mapIntWithSideEffect(f func(int) int, xs []int) []int {
for i, e := range xs {
xs[i] = f(e)
}
return xs
}
func main() {
execute(hello)
execute(hello2(2))
fmt.Printf("%d \n", apply(1, inc))
xs := []int{1, 2, 3}
ys := mapInt(inc, xs)
zs := mapInt(square, xs)
fmt.Printf("%d %d %d \n", xs[1], ys[1], zs[1])
}Anonymous functions are function without a name. They originate from the lambda calculus and therefore we refer to anonymous functions also as lambda funtions.
Function definitions so far.
Go supports anonymous functions (lambdas).
Almost same syntax as for function definitions. We simply drop the function name.
How can we use lambda functions?
Instead of
we can also write
Advantage:
Define and use a lambda function “in place”
Lambda functions can be defined anywhere and may refer to local variables
Consider
func() result is a parameter-less function with not
result value
func () { fmt.Printf("Hello %d \n", x) } lambda
function where we refer to the local variable x
package main
import "fmt"
func apply(i int, f func(int) int) int {
return f(i)
}
func execute(f func()) {
f()
}
func hello() {
fmt.Print("Hello \n")
}
func hello2(x int) func() {
fmt.Printf("%d ", x)
return hello
}
// Create a new slice.
func mapInt(f func(int) int, xs []int) []int {
ys := make([]int, len(xs))
for i, e := range xs {
ys[i] = f(e)
}
return ys
}
func main() {
execute(hello)
execute(hello2(2))
fmt.Printf("%d \n", apply(1, func(i int) int { return i + 1 }))
xs := []int{1, 2, 3}
ys := mapInt(func(i int) int { return i + 1 }, xs)
zs := mapInt(func(i int) int { return i * i }, xs)
fmt.Printf("%d %d %d \n", xs[1], ys[1], zs[1])
}Recall
Consider
f(1) yields a function of type
func(int) int
This function is effectively the increment function
Consider
The call (f(2))(3) calculates
2 + 3
First we call f(2)
This yields function of type func(int) int
Hence, we call this function with parameter 2
f(2) is an example of a partial function
application (because we obtain another function as the result).
To avoid clutter, the convention adopted in Go (and other languages that support higher-order functions) is to allow removal of parentheses by assuming that
function application is left-associative
function types are right-associative
Compare
versus
Both are the same!
func(int) func(int) int = func(int) (func(int) int)
because function types are right-associative
f(2)(3) = (f(2))(3)
because function application is left-associative
package main
import "fmt"
func add(x int) func(int) int {
return func(y int) int { return x + y }
}
func main() {
var f func(int) (func(int) int)
f = add
inc := f(1)
fmt.Printf("\n %d", inc(3))
x := (f(2))(3)
fmt.Printf("\n %d", x)
var g func(int) func(int) int
g = add
y := g(2)(3)
fmt.Printf("\n %d", y)
}Consider
The arguments to plus must be both present.
In Go, it’s possible to ‘incrementally’ supply addition with its arguments.
Function add expects an integer argument (left operand)
and yields a function. This function expects another integer argument
(right operand) and then yields the expected result.
How to transform one function into the other? See below
curry and uncurry functions.
“Curried style”
Arguments can be supplied incrementally
Allows for a more flexible use of functions
“Uncurried style”
All arguments must be present
Generally less run-time overhead
package main
import "fmt"
func plus(x int, y int) int {
return x + y
}
func add(x int) func(int) int {
return func(y int) int {
return x + y
}
}
func curry(f func(int, int) int) func(int) func(int) int {
return func(x int) func(int) int {
return func(y int) int {
return f(x, y)
}
}
}
func uncurry(g func(int) func(int) int) func(int, int) int {
return func(x int, y int) int {
return (g(x))(y)
}
}
func main() {
x1 := plus(1, 2)
x2 := (add(1))(2)
p := uncurry(add)
x3 := p(1, 2)
p2 := curry(plus)
x4 := (p2(1))(2)
fmt.Printf("%d %d %d %d", x1, x2, x3, x4)
}What’s the output of the following program?
package main
import "fmt"
type Type0 func()
func main() {
var fn Type0
var x int = 2
fn = func() { fmt.Printf("%d \n", x) }
fn()
x = 3
fn()
}For each call to fn there is a different result!
Unlike pure functional programming languages, the Go language is impure. Pure means that for each function call and the same input arguments, we obtain the same output. This is also referred to as referential transparency.
Introduces a new type named myint. Values of type
int and myint cannot be mixed.
The type myint is structurally isomorphic to
int, so we can (type) convert one into the other.
Here is the complete example.
package main
import "fmt"
type myint int
func main() {
var i int
var j myint
j = 1
i = (int)(j)
// i = j
// yields a type error
j = (myint)(i)
fmt.Printf("%d", i)
}Defines a named type rectangle that is structurally
isomorphic to a struct of two ints where both ints can be accessed via
field labels.
Construction of values.
var r1 rectangle = rectangle{1, 2}
var r2 rectangle = rectangle{width: 2, length: 1}
r3 := rectangle{width: 2, length: 1}Either by position or by field reference.
Control structures
Arrays, slices
Functions
func add(x int) func(int) int {
return func(y int) int { return x + y }
}
// func add ... Function declaration, I'm a function with name add
// func(int) int Function type, from int to int
// func(y int) int { ...} Anonymous function, lambda binder and bodyFunction application is left-associative.
(add(1))(2) versus add(1)(2)
Functions types are right-associative.
func(int) (func(int) int) versus f func(int) func(int) int
Connection to OO. In Go functions are first-class (that is, they can appear anywhere). This is similar to OO where objects are first-class.
For example, in an OO language, we can call a method m1
on some object o1. The result is an object on which we call
another method m2.
Play with the examples provided.
Implement the following functions. Sample solutions below.
// Yields the first element which satisfies the predicate.
// Otherwise, we return a dummy element.
// The first component of the resulting tuple indicates
// if any element has satisfied the predicate.
func any(p func(int) bool,xs []int) (bool,int)
// Map f over a list of integer values which then yields
// a list of booleans
func map(f func(int) bool,xs []int) []bool
// Filter out all elements which satisfy the predicate.
func filter(p func(int) bool,xs []int) []intpackage main
import "fmt"
// Yields the first element which satisfies the predicate.
// Otherwise, we return a dummy element.
// The first component of the resulting tuple indicates
// if any element has satisfied the predicate.
func any(p func(int) bool, xs []int) (bool, int) {
for _, x := range xs {
if p(x) {
return true, x
}
}
return false, -1
}
// Map f over a list of integer values which then yields
// a list of booleans
// 'map' predefined so use mapMy
func mapMy(f func(int) bool, xs []int) []bool {
var ys []bool
for _, x := range xs {
ys = append(ys, f(x))
}
return ys
}
// Filter out all elements which satisfy the predicate.
func filter(p func(int) bool, xs []int) []int {
var ys []int
for _, x := range xs {
if p(x) {
ys = append(ys, x)
}
}
return ys
}
func main() {
b, x := any(func(x int) bool { return x > 1 }, []int{0, 1, 2, 3})
fmt.Printf("%b %d \n", b, x)
xs := filter(func(x int) bool { return x > 1 }, []int{0, 1, 2, 3})
for _, x := range xs {
fmt.Printf("%d \n", x)
}
}The lambda calculus represents a minimal core language just consisting of functions.
Functions are first-class (can appear as arguments and as return values). Think of first-class objects!
Terms (expressions/programs) in the lambda calculus are defined by the following (abstract) EBNF syntax.
where
The above EBNF is ambiguous. For example, consider expression . Based on the above EBNF rules there are two possible interpretations.
A lambda function with formal parameter where is applied to itself in the function body.
The identity function applied to .
The first interpretation can be explained via the following (parse tree) derivation
whereas for the second derivation we find
In the above derivation, parentheses “(..)” are meta-symbols to highlight the difference among the two derivations.
In examples, we therefore may make use of parentheses to avoid ambiguities, for example .
The operational semantics of the lambda calculus is defined via a single rule, commonly referred to as -reduction.
In , we apply the substitution : In the body , replace each occurrence of the formal parameter by the argument .
Commonly, we refer to as a lambda-abstraction and to as the lambda-binding.
For example, reduces to .
As it is common in programming languages, we can reuse the same variable names and apply the common rule that each use of a variable name refers to the closest binding site. For example, consider where the innermost occurrence of refers to the inner lambda-binding.
Via some simply renaming we can always guarantee that variables are distinct. For example, can be renamed to .
We generally assume that lambda-bindings always introduce fresh, distinct variables.
Function application is left associative. Hence, the term is a short-hand for .
The body of a lambda abstractions extends to the right as long as possible. Hence, the term is a short-hand for .
computes all free variables in , i.e. those variables which are not bound by a lambda abstraction.
The definition by induction over the structure of is as follows:
In case of name clashes we must rename bound variables. Consider :
Examples (where refers to a term/expression)
There are two principles ways how to evaluate terms:
(\x-> ...) eCall-by value (CBV) = AOR with the exception that we don’t evaluate under “lambda” (i.e. inside function bodies)
Difference between CBV and AOR illustrated via an example. Instead of the plain lambda calculus we make use of Go.
Under AOR, we can reduce f(1+2) to f(3) and
inc(1+1) to inc(2).
Under CBV, we can only reduce f(1+2) to
f(3) because inc(1+1) is inside a function
body.
Hence, evaluation under CBV proceeds as follows.
f(1+2)
--> f(3)
--> inc(1+1) + 3
--> inc(2) + 3
--> 3 + 3
--> 6
In comparison, evaluation under AOR.
f(1+2)
= (\lambda x. inc(1+1) + x) (1+2)
^^^
innermost redex
--> (\lambda x. inc(2) + x) (1+2)
--> (\lambda x. inc(2) + x) 3
--> inc(2) + 3
...
--> 6
The motivation for CBV is to avoid evaluation of arguments unless they are needed to make progress during evaluation.
Consider the following variant.
func inc(x int) int { return x+1 }
func f(x int) int {
if x == 3 {
return 0
}
return inc(1+1) + x
}
func main() {
f(1+2)
}For this case and the call f(1+2) reducing
inc(1+1) to inc(2) is unnecessary (and
therefore wasteful) as we don’t reach this program text during
evaluation.
Outermost, leftmost, also called Normal Order Reduction (NOR)
Call-by name (CBN) = NOR with the exception that we don’t evaluate under “lambda” (i.e. inside function bodies)
We consider evaluation of where denotes an evaluation step.
We omit the alpha-renaming step (we should actually rename bound variables but keeping the bound variables as they are is harmless for this example).
Call-by-name (CBN)
Call-by-value (CBV):
There are lambda terms whose evaluation will not terminate:
Termination under CBN is more likely than termination under CBV.
Let’s abbreviate by . Then, the lambda term terminates under CBN but not under CBV.
CBV:
CBN:
call-by-need:
The difference to CBN is that once we evaluate and obtain the result , we immediately replace all other occurrences of by without actually evaluating these other occurrences.
Consider .
Function application is left-associative, therefore, the meaning of the above lambda term is .
Let’s carry out the evaluation steps.
because
because
On the other hand, the lambda term evaluates to . Briefly, the evaluation steps are
How expressive is the lambda calculus? As expressive as a Turing machine?
Yes! But how? The lambda calculus looks rather simple. There are no numbers, booleans, no conditional, looping constructs.
Indeed, but as we will show next, we can encode data types (booleans, …), conditional expressions, recursion.
The idea of the Church encoding (named after Alonzo Church):
The idea:
not x = if x then false else true
x or y = if x then true else y
x and y = if x then y else false
The actual encoding:
true =
false =
if then else =
which written in ‘lambda notation’ is
ite =
Example: if true then else equals
In detail:
=
by adding parentheses (recall that function application is left associative)
We evaluate . The expect result is . Let’s see!
Several redexes. We choose the outermost redex
The idea.
…
The successor function.
The addition function.
What about recursion?
Idea:
Fixpointcombinator
This works!
More encodings (pairs, natural numbers) are possible. See Church encoding.