% -*- mode: latex; coding: utf-8; TeX-master: ../thesis -*- % !TEX TS-program = pdflatexmk % !TEX encoding = UTF-8 Unicode % !TEX root = ../thesis.tex \section{Demonstration of the new built-in functions} The new built-in functions can be used in the same manner as the existing built-ins like \mintinline{go}|append|. This example serves as a demonstration by modifying a slice of integers, although they can be used to modify a slice of any type. \begin{code} \begin{gocode} package main import ( "fmt" "strconv" ) // Counterpart to Haskell's `derive Show` through code generation //go:generate stringer -type parity type parity int const even parity = 0 const odd parity = 1 // shouldBe returns a function that returns true if an int is of the // given parity func shouldBe(p parity) func(i int) bool { return func(i int) bool { return i%2 == int(p) } } func main() { lst := []int{1, 2, 3, 4, 5} lstMult := fmap(func(i int) int { return i * 5 }, prepend(0, lst)) addToString := func(s string, i int) string { return s + strconv.Itoa(i) + " " } // fold over even / odd numbers and add them to a string evens := foldl(addToString, even.String()+": ", filter(shouldBe(even), lstMult)) odds := foldl(addToString, odd.String()+": ", filter(shouldBe(odd), lstMult)) fmt.Println(evens, odds) // even: 0 10 20 odd: 5 15 25 } \end{gocode} \caption{Demonstration of the new built-in functions\label{code:funcexample}} \end{code} \section{Refactoring the Prettyprint Package} \newglossaryentry{stdout}{name=stdout, description={Standard Output, the default output stream for programs}} The code blocks~\ref{code:assign-pos} and~\ref{code:func-reassign} have been generated by a small package `prettyprint' contained in the funcheck repository. To see how the newly built-in functions and funcheck can be used, this `prettyprint' package can be refactored to a purely functional implementation. The current version of the package is written in what could be considered idiomatic Go\footnote{ There is no exact definition of what idiomatic Go is, so this interpretation could be challenged. It is idiomatic Go code to the author. }. The prettyprinter is based on the same framework as assigncheck\footnote{Assigncheck is the main package for funcheck and checks the reassignments}, but instead of reporting anything, it prints AST information to \gls{stdout}. Similarly to assigncheck, the main logic of the package is within a function literal that is being passed to the \mintinline{go}|ast.Inspect| function. Prettyprint only checks two AST node types, \mintinline{go}|*ast.DeclStmt| and \mintinline{go}|*ast.AssignStmt| (declarations and assignments). For example, for the program \begin{gocode} package main import "fmt" func main() { x, y := 1, 2 y = 3 fmt.Println(x, y) } \end{gocode} the following AST information is printed: \begin{gocode} Assignment "x, y := 1, 2": 2958101 Ident "x": 2958101 Decl "x, y := 1, 2": 2958101 Ident "y": 2958104 Decl "x, y := 1, 2": 2958101 Assignment "y = 3": 2958115 Ident "y": 2958115 Decl "x, y := 1, 2": 2958101 \end{gocode} To refactor it to a purely functional version, funcheck can be used to list reassignments: \begin{bashcode} $> funcheck . prettyprint.go:20:2: internal reassignment (for loop) in "for _, file := range pass.Files { ... }" prettyprint.go:42:2: internal reassignment (for loop) in "for i := range decl.Specs { ... }" prettyprint.go:67:2: internal reassignment (for loop) in "for _, expr := range as.Lhs { ... }" \end{bashcode} As can be seen in the output, the package uses 3 \mintinline{go}|for| loops to range over slices. However, there are no other reassignments of variables in the code. The code to print declarations, which causes the second lint message, is as shown in code block~\ref{code:decl-printing}. \begin{code} \begin{gocode} func checkDecl(as *ast.DeclStmt, fset *token.FileSet) { fmt.Printf("Declaration %q: %v\n", render(fset, as), as.Pos()) decl, ok := as.Decl.(*ast.GenDecl) if !ok { return } for i := range decl.Specs { val, ok := decl.Specs[i].(*ast.ValueSpec) if !ok { continue } if val.Values != nil { continue } if _, ok := val.Type.(*ast.FuncType); !ok { continue } fmt.Printf("\tIdent %q: %v\n", render(fset, val), val.Names[0].Pos()) } } \end{gocode} \caption{Pretty-printing declarations in idiomatic Go\label{code:decl-printing}} \end{code} To convert this for-loop appropriately, the new built-in `foldl' can be used. To recapitulate, the `foldl' function is being defined as: \begin{gocode} func foldl(fn func(Type1, Type) Type1, acc Type1, slice []Type) Type1 \end{gocode} As `foldl' requires a return type, a dummy type `null" can be introduced, which is just an empty struct: \begin{gocode} type null struct{} \end{gocode} Now the code within the for loop can be used to create a function literal: \begin{gocode} check := func(_ null, spec ast.Spec) (n null) { // implementation } \end{gocode} There are two subtleties in regards to the introduced null type: First, the null value that is being passed as an argument is being discarded by the use of an empty identifier. Secondly, the return value is `named', which means the variable `n' is already declared in the function block. Because of this, `naked returns' can be used, so there is no need to specify which variable is being returned. The code snippet~\ref{code:decl-printing} can be translated to: \begin{code} \begin{gocode} func checkDecl(as *ast.DeclStmt, fset *token.FileSet) { fmt.Printf("Declaration %q: %v\n", render(fset, as), as.Pos()) check := func(_ null, spec ast.Spec) (n null) { val, ok := spec.(*ast.ValueSpec) if !ok { return } if val.Values != nil { return } if _, ok := val.Type.(*ast.FuncType); !ok { return } fmt.Printf("\tIdent %q: %v\n", render(fset, val), val.Names[0].Pos()) return } if decl, ok := as.Decl.(*ast.GenDecl); ok { _ = foldl(check, null{}, decl.Specs) } } \end{gocode} \caption{Pretty-printing declarations in functional Go} \end{code} The for-loop has been replaced by a `foldl', where a function closure that contains the actual processing is passed. While this still looks similar to the original example, this is mostly due to the `if' statements. In Haskell, pattern matching would be used and nil checks could be omitted entirely. Also, as Haskell's type system is more advanced, the handling of those types would be different too. However, the goal of this thesis is to make functional code look more familiar to programmers that are used to imperative code. And while it may not look like it, the code does not use any mutation of variables\footnote{Libraries may do, but the scope is not to rewrite any existing libraries.}, for loops or global state. Therefore, it can be concluded that this snippet is purely functional as per the definition from Chapter~\ref{sec:func-purity}. \section{Quicksort} In Chapter~\ref{code:haskell-quicksort}, a naive implementation of the Quicksort sorting algorithm has been introduced. Implementing this algorithm in Go is now straightforward and the similarities between the Haskell implementation and the functional Go implementation are striking: \begin{listing} \begin{gocode} func quicksort(p []int) []int { if len(p) == 0 { return []int{} } lesser := filter(func(x int) bool { return p[0] > x }, p[1:]) greater := filter(func(x int) bool { return p[0] <= x }, p[1:]) return append(quicksort(lesser), prepend(p[0], quicksort(greater))...) } \end{gocode} \begin{haskellcode} quicksort :: Ord a => [a] -> [a] quicksort [] = [] quicksort (p:xs) = (quicksort lesser) ++ [p] ++ (quicksort greater) where lesser = filter (< p) xs greater = filter (>= p) xs \end{haskellcode} \caption{Quicksort implementations compared} \end{listing} Again, the Go implementation bridges the gap between being imperative and functional, while still being obvious about the algorithm. Furthermore, as expected, when inspecting the code with funcheck, no non-functional constructs are reported. \section{Comparison to Java Streams} In Java 8, concepts from functional programming have been introduced to the language. The major new feature was Lambda Expressions --- anonymous function literals --- and streams. Streams are an abstract layer to process data in a functional way, with `map', `filter', `reduce' and more. Java Streams are similar to the new built-in functions in this thesis: \begin{listing} \begin{javacode} List even = list.stream() .filter(x -> x % 2 == 0) .collect(Collectors.toList()); \end{javacode} \begin{gocode} even := filter( func(x int) bool { return x%2 == 0 }, list) \end{gocode} \caption{Comparison Java Streams and functional Go} \end{listing} The lambda-syntax in Java is more concise than Go's function literals, where the complete function header has to be provided\footnote{There is an open proposal to add a lightweight anonymous function syntax to Go 2, which, if implemented, would resolve this verbosity\autocite{go-lambdas}}. However, the conversion to a stream and back to a list (with \mintinline{java}|list.stream()| and \mintinline{java}|.collect(Collectors.toList())|) is not required in Go, reducing the mental overhead for the programmer. Apart from syntactical differences, Java Streams contain all the functions that have been added as built-ins to Go, and a lot more. On the other hand, Java's Syntax is arguably more complex than Go. An indicator for this might be the language specification; Go's Language Specification is roughly 110 pages, while Java's specification spans more than 700 pages\footnote{ The Java 8 Specification is 724\autocite{java-8-spec}, the Java 14 Specification 774\autocite{java-14-spec} pages.}, more than 6 times the size. The consideration of which language to choose comes down to the experience with either language. An experienced Java programmer will find it easier to start with Java's toolset, while programmers coming from a C background may choose Go.