Functional Programming
Concepts of Programming Languages
Sebastian Macke
Rosenheim Technical University
Sebastian Macke
Rosenheim Technical University
func divide(a, b int) (int, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil } func main() { // Successful division result, err := divide(10, 0) if err != nil { fmt.Println("Error:", err) } else { fmt.Println("Result:", result) } }
The error is just an interface with one method that should return the error message:
type error interface { Error() string }
Functional programming is old, but some major languages have adapted them quite recently
These types of functional languages support only the functional paradigms and have no state. For example − Haskell.
These types of functional languages support the functional paradigms and imperative style programming. For example all of the major languages today.
6func main() { ADD := func(x, y int) int { return x + y } fmt.Println(ADD(1, 2)) // -> returns 3 fmt.Println("The type of ADD is", reflect.TypeOf(ADD).Kind()) }
ANOTHERADD := ADD; ANOTHERADD(1, 2);
func main() { fmt.Println(func(x, y int) int { return x + y }(3, 4)) // -> returns 7 }
func do(f func(int), loops int) { for i := 0; i < loops; i++ { f(i) } } func main() { printvalue := func(i int) { fmt.Println(i) } do(printvalue, 5) }
type areaFunc func(int, int) int func getAreaFunc() areaFunc { return func(x, y int) int { return x * y } } func main() { areaF := getAreaFunc() res := areaF(2, 4) fmt.Println(res) }
array := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11} filteredArray := []int{} for _, v := range array { if v%2 == 0 { filteredArray = append(filteredArray, v) } } fmt.Println(filteredArray)
array := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11} filteredArray := stream.OfSlice(array). Filter(func(n int) bool { return n%2 == 0 }). ToSlice() fmt.Println(filteredArray)
Java:
class IntComparator implements Comparator<Integer> { @Override public int compare(Integer i1, Integer i2) { return i1.compareTo(i2); } } class SortDemo { public static void main(String[] args) { Integer[] array = { 3, 2, 1, 5, 8, 6 }; Arrays.sort(array, new IntComparator()); System.out.println(Arrays.toString(array)); } }
array := []int{10, 5, 3, 7, 1, 0, 4, 6} sortedArray := stream.OfSlice(array). Sorted(func(a, b int) int { return a - b }). ToSlice() fmt.Println(sortedArray)
// intSeq returns another function, which we define anonymously in the body of intSeq. // The returned function closes over the variable i to form a closure. func intSeq() func() int { i := 0 return func() int { i++ return i } } func main() { // We call intSeq, assigning the result (a function) to nextInt. // This function value captures its own i value, which will be updated each time we call nextInt. nextInt := intSeq() // See the effect of the closure by calling nextInt a few times. fmt.Println(nextInt()) fmt.Println(nextInt()) // To confirm that the state is unique to that particular function, create and test a new one. newInts := intSeq() fmt.Println(newInts()) }
Access array outside of the function for sorting
func main() { data := []int{27, 15, 8, 9, 12, 4, 17, 19, 21, 23, 25} sort.SliceStable(data, func(i, j int) bool { return data[i] < data[j] }) fmt.Println(data) }
This is a Bubble Sort algorithm for ints.
func BubbleSort(data []int) { for i := 0; i < len(data); i++ { for j := 0; j < len(data)-1; j++ { if data[j] > data[j+1] { data[j], data[j+1] = data[j+1], data[j] // Swap the values } } } } func main() { data := []int{27, 15, 8, 9, 12, 4, 17, 19, 21, 23, 25} BubbleSort(data) fmt.Println(data) }
Rewrite it, so that the Bubble Sort algorithm takes a comparison function as input
BubbleSort(data, func(i, j int) bool {return data[i] > data[j]})
const object1 = { prop: 42, func: function () { return this.prop; }, }; console.log(object1.prop) // 42 console.log(object1.func()) // 42 // At creation time, this points to test.prop const object2 = { prop: 43, func: object1.func // take the function defined in object1. } console.log(object2.prop) // 43 console.log(object2.func()) // 43. Now "this" points to object2.
Examples: exp, sin, cos, max, min.
Advantages
var count = 0 func increment() { count++ }
func incrementPure(count int) int { return count + 1 // This function is pure because it does not modify external state }
var array []int newarray := append(array, 1)
The most prominent characteristics of functional programming are as follows
Functional programming does not support state, so there are no side-effect results and we can write error-free codes.
Functional programs consist of independent units that can run concurrently. As a result, such programs can be more efficient.
Functional programming supports lazy evaluation like Lazy Lists, Lazy Maps, etc.
Functional programming supports distributed computing
23// ADD with 2 parameters ADD := func(x, y int) int { return x + y }
ADD(1,2) -> 3
// Curried ADD ADDC := func(x int) func(int) int { return func(y int) int { return x + y } }
ADDC(1)(2) -> 3
// Curried ADD ADDC := func(x int) func(int) int { return func(y int) int { return x + y } }
partialApplicatedFunction := ADDC(1) .... Do something else partialApplicatedFunction(3) -> 4
Functions can be composed to new functions
g(f(x)) -> (g ◦ f)(x)
// Function f() f := func(x int) int { return x * x } // Function g() g := func(x int) int { return x + 1 } // Functional Composition: (g◦f)(x) gf := func(x int) int { return g(f(x)) } fmt.Printf("%v\n", gf(2)) // --> 5
Functions can be composed with functions as parameters
package main import "fmt" type function func(any) any func main() { compose := func(g, f function) function { return func(x any) any { return g(f(x)) } } square := func(x any) any { return x.(int) * x.(int) } fmt.Printf("%v\n", compose(square, square)(2)) // --> 4*4 = 16 fmt.Printf("%v\n", compose(compose(square, square), square)(2)) }
Professor Graham Hutton explains the Lambda Calculus (Cool Stuff :-)
29Format
function (input) { return output }
That's all
30
Variables
x
- A variable can be itself a function
- There are no datatypes (number, logical values)
Functions
λx.x (here the identity function as an example)
- Functions have no internal state
Applications
(λx.x) a
- here the application of the identity function to a returns a)
- also a can be a function
- Parenthesis help to avoid ambiguity.
// This is the key: A recursive function definition for all functions!!! type fnf func(fnf) fnf func main() { // Boolean TRUE as function: λx.λy.x TRUE := fnf(func(x fnf) fnf { return func(y fnf) fnf { return x } }) // Boolean FALSE as function: λx.λy.y FALSE := fnf(func(x fnf) fnf { return func(y fnf) fnf { return y } }) fmt.Println(TRUE.ToBool()) fmt.Println(FALSE.ToBool()) }
// ToBool Returns an actual bool for a Church Boolean func (f fnf) ToBool() bool { // λx.x is a function which returns itself (the ID) ID := func(x fnf) fnf { return x } var ret bool f(func(f fnf) fnf { ret = true; return f })(func(f fnf) fnf { ret = false; return f })(ID) return ret }
// Boolean Logic NOT := func(p fnf) fnf { return p(FALSE)(TRUE) } // λb.b False True AND := func(p fnf) fnf { return func(q fnf) fnf { return p(q)(p) } } OR := func(p fnf) fnf { return func(q fnf) fnf { return p(p)(q) } } EQ := func(p fnf) fnf { return func(q fnf) fnf { return p(q)(NOT(q)) } } fmt.Println(NOT(FALSE).ToBool()) fmt.Println(NOT(TRUE).ToBool()) fmt.Println(AND(FALSE)(TRUE).ToBool()) fmt.Println(OR(FALSE)(FALSE).ToBool()) fmt.Println(EQ(TRUE)(TRUE).ToBool()) }
TRUE = a => b => a; FALSE = a => b => b; NOT = f => a => b => f(b)(a); ToBool = f => f(() => true)(() => false)() console.log( ToBool(TRUE) ) // -> true console.log( ToBool(FALSE) ) // -> false console.log( ToBool(NOT(TRUE)) ) // -> false console.log( ToBool(NOT(FALSE)) ) // -> true
Fundamentals of Lambda Calculus & Functional Programming in JavaScript
35// Map returns a copy of the string s with all its characters modified // according to the mapping function. If mapping returns a negative value, the character is // dropped from the string with no replacement. func Map(mapping func(rune) rune, s string) string
s := "Hello, world!" s = strings.Map(func(r rune) rune { return r + 1 }, s) fmt.Println(s) // --> Ifmmp-!xpsme"
// array of generic interfaces. stringSlice := []any{"a", "b", "c", "1", "D"} // Map/Reduce result := ToStream(stringSlice). Map(toUpperCase). Filter(notDigit). Reduce(concat).(string) if result != "A,B,C,D" { t.Error(fmt.Sprintf("Result should be 'A,B,C,D' but is: %v", result)) } // lambda (inline)
Map/Reduce is a famous functional construct implemented in many parallel and distributed collection frameworks like Hadoop, Apache Spark, Java Streams (not distributed but parallel), C# Linq
type Stream interface { Map(m Mapper) Stream Filter(p Predicate) Stream Reduce(a Accumulator) Any }
// Predicate function returns true if a given element should be filtered. type Predicate func(any) bool // Mapper function maps a value to another value. type Mapper func(o1 any) any // Accumulator function returns a combined element. type Accumulator func(any, any) any
Word Count is a famous algorithm for demonstrating the power of distributed collections and functional programming. Word Count counts how often a word (string) occurs in a collection. It is easy to address that problem with shared state (a map), but this solution does not scale well. The question here is how to use a pure functional algorithm to enable parallel and distributed execution.
After running Word Count, you should get the following result:
INPUT: []Any{"a", "a", "b", "b", "D", "a"} OUTPUT: "a:3, b:2, D:1, "
Questions
// Classic wordcount sample // ======================== func TestWordCount(t *testing.T) { strings := []any{"a", "a", "b", "b", "D", "a"} // Map/Reduce result := ToStream(strings). Map(func(o any) any { result := []Pair{Pair{o, 1}} return result }). Reduce(sumInts).([]Pair) for _, e := range result { fmt.Printf("%v:%v, ", e.k, e.v) // "a:3, b:2, D:1, " } }