You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
425 lines
14 KiB
425 lines
14 KiB
5 years ago
|
\section{Information about this thesis}
|
||
|
|
||
|
This thesis, including this document, is contained in a single git repository
|
||
|
that can be found at GitHub\autocite{git-repo}.
|
||
|
|
||
|
All the work that has been done is open sourced under the Apache License 2.0,
|
||
|
except the Go source code, which has its own license.
|
||
|
|
||
|
To view the code, build or reproduce results, this git repository can be cloned
|
||
|
and its submodules checked out. The version that has been submitted can be checked
|
||
|
out through the tag `v1.0.0':
|
||
|
\begin{bashcode}
|
||
|
$> git clone https://github.com/tommyknows/bachelor-thesis.git
|
||
|
$> cd bachelor-thesis
|
||
|
$> git checkout v1.0.0
|
||
|
$> git submodule init
|
||
|
$> git submodule update
|
||
|
\end{bashcode}
|
||
|
|
||
|
The directory `thesis' contains the \LaTeX\ code for this paper. There is a helper
|
||
|
required for displaying code in the thesis which is located in `thesis/utils' and
|
||
|
needs to be built with `go build .'. However, a PDF which should be on the same state
|
||
|
as the source is provided in the repository too (`thesis/thesis.pdf').
|
||
|
|
||
|
The `work' directory contains code that has been developed in this thesis. In this
|
||
|
directory, the Go and funcheck source code can be found (see Appendix~\ref{appendix:install-fgo}
|
||
|
and~\ref{appendix:build-funcheck}). Further, some examples
|
||
|
of functional Go have been developed in the `example' directory. The `common-list-functions'
|
||
|
folder contains the script as mentioned in Appendix~\ref{appendix:function-occurrences}.
|
||
|
|
||
|
\section{Example for Functional Options}\label{appendix:funcopts}
|
||
|
|
||
|
This source code example demonstrates `functional options' as introduced in this paper.
|
||
|
Functional options are usually passed to a constructor to configure the new instance, in
|
||
|
this case a web server. The advantages of functional options are that the API ends up to
|
||
|
be cleaner and more easily extensible and allows the default use-case to be as simple
|
||
|
as possible.
|
||
|
|
||
|
\begin{code}
|
||
|
\gofile{../work/examples/functional-options/main.go}%
|
||
|
\caption{Functional Options for a simple web server}
|
||
|
\end{code}
|
||
|
|
||
|
\section{Analysis of function occurrences in Haskell code}\label{appendix:function-occurrences}
|
||
|
The results of the analysis have been acquired by running the `count-function' script
|
||
|
that is located in `work/common-list-functions' in the git repository\autocite{git-repo}.
|
||
|
|
||
|
The script utilises \href{https://github.com/BurntSushi/ripgrep}{ripgrep} to count the number of occurrences, so
|
||
|
this must be installed in order to run this script.
|
||
|
|
||
|
\begin{bashcode}
|
||
|
./count-function.sh ":" "((map)|(fmap))" "((foldr)|(foldl'?))" "filter" "reverse" "take" "drop" "sum" "zip" "product" "maximum" "min
|
||
|
imum"
|
||
|
Searching for occurrences in subdirectories of ./common-list-functions
|
||
|
Found 2912 occurrences of ":"
|
||
|
Found 1873 occurrences of "((map)|(fmap))"
|
||
|
Found 303 occurrences of "((foldr)|(foldl'?))"
|
||
|
Found 262 occurrences of "filter"
|
||
|
Found 154 occurrences of "reverse"
|
||
|
Found 108 occurrences of "take"
|
||
|
Found 81 occurrences of "drop"
|
||
|
Found 44 occurrences of "sum"
|
||
|
Found 38 occurrences of "zip"
|
||
|
Found 15 occurrences of "product"
|
||
|
Found 45 occurrences of "maximum"
|
||
|
Found 10 occurrences of "minimum"
|
||
|
\end{bashcode}
|
||
|
|
||
|
The terms are searched with a leading and trailing space to get exact matches. Further, as can be seen in
|
||
|
the call to the script, the search combines the results of map together with fmap, and foldr with foldl
|
||
|
and foldl'.
|
||
|
|
||
|
\section{Mutating variables in Go}\label{appendix:mutation}
|
||
|
|
||
|
Source Code~\ref{code:mutation} shows how pointers are used to mutate data in Go. This does
|
||
|
not necessarily need to be a `struct' type, pointers can be used of any type.
|
||
|
Pointers have to be used to modify values because Go is a copy-by-value language and thus
|
||
|
copies the parameters that are passed to a function.
|
||
|
|
||
|
\begin{code}
|
||
|
\gofile{../work/examples/mutate/main.go}%
|
||
|
\caption{Example on how to mutate data in Go}\label{code:mutation}
|
||
|
\end{code}
|
||
|
|
||
|
\section{Shadowing variables in Go}\label{appendix:shadowing}
|
||
|
|
||
|
Source Code~\ref{code:shadowing} demonstrates the block scoping and shadowing rules
|
||
|
in Go.
|
||
|
|
||
|
\begin{code}
|
||
|
\gofile{../work/examples/shadowing/main.go}%
|
||
|
\caption{Example on how shadowing works on block scopes}\label{code:shadowing}
|
||
|
\end{code}
|
||
|
|
||
|
\section{Foldl and Foldl' difference}\label{appendix:foldl-strictness}
|
||
|
|
||
|
This code example shows the difference between foldl and foldl' in their
|
||
|
execution. What can be seen is that foldl builds up a call stack, while
|
||
|
foldl' executes the calls during the traversal.
|
||
|
|
||
|
\begin{code}
|
||
|
\begin{haskellcode}
|
||
|
> (?) :: Int -> Int -> Int
|
||
|
> _ ? 0 = 0
|
||
|
> x ? y = x*y
|
||
|
>
|
||
|
> list :: [Int]
|
||
|
> list = [2,3,undefined,5,0]
|
||
|
>
|
||
|
> foldl (?) 1 list
|
||
|
foldl (?) 1 [2,3,undefined,5,0] -->
|
||
|
foldl (?) (1 ? 2) [3,undefined,5,0] -->
|
||
|
foldl (?) ((1 ? 2) ? 3) [undefined,5,0] -->
|
||
|
foldl (?) (((1 ? 2) ? 3) ? undefined) [5,0] -->
|
||
|
foldl (?) ((((1 ? 2) ? 3) ? undefined) ? 5) [0] -->
|
||
|
foldl (?) (((((1 ? 2) ? 3) ? undefined) ? 5) ? 0) [] -->
|
||
|
((((1 ? 2) ? 3) ? undefined) ? 5) ? 0 -->
|
||
|
0
|
||
|
|
||
|
> foldl' (?) 1 list
|
||
|
foldl' (?) 1 [2,3,undefined,5,0] -->
|
||
|
1 ? 2 --> 2
|
||
|
foldl' (?) 2 [3,undefined,5,0] -->
|
||
|
2 ? 3 --> 6
|
||
|
foldl' (?) 6 [undefined,5,0] -->
|
||
|
6 ? undefined -->
|
||
|
*** Exception: Prelude.undefined
|
||
|
\end{haskellcode}
|
||
|
\caption{foldl and foldl' strictness\autocite{fold-types}}
|
||
|
\end{code}
|
||
|
|
||
|
\section{Workaround for the missing foldl implementation in Go}\label{appendix:foldl-go}
|
||
|
|
||
|
This code block exemplifies how foldl could be implemented in Go code. It is based on the example
|
||
|
from Appendix~\ref{appendix:foldl-strictness}, rewritten in Go. While Go does not know about
|
||
|
the concept of laziness, the programmer may implement the laziness himself by working with function
|
||
|
closures.
|
||
|
|
||
|
In this example, \mintinline{go}|*int| is used instead of \mintinline{go}|int| to simulate Haskell's
|
||
|
\mintinline{haskell}|undefined| with a nil-pointer.
|
||
|
If a nil-pointer is dereferenced, the program will panic.
|
||
|
|
||
|
In the lazy version of this code (utilising `myFold' and `mulLazy'), the panic does not occur because
|
||
|
the nil-pointer is never dereferenced as the function closure is never executed. This is equal
|
||
|
to the `foldl' demonstration in Appendix~\ref{appendix:foldl-strictness}.
|
||
|
|
||
|
The non-lazy version (`foldl' and `mul') executes the function while traversing the slice and thus panics.
|
||
|
|
||
|
\begin{code}
|
||
|
\gofile{../work/examples/foldl-workaround/main.go}%
|
||
|
\begin{bashcode}
|
||
|
$> fgo run .
|
||
|
0
|
||
|
panic: runtime error: invalid memory address or nil pointer dereference
|
||
|
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x109e945]
|
||
|
|
||
|
goroutine 1 [running]:
|
||
|
...
|
||
|
\end{bashcode}
|
||
|
\caption{Working around the missing foldl implementation in Go\label{code:foldl-go}}
|
||
|
\end{code}
|
||
|
|
||
|
\section{Prettyprint implementation}\label{appendix:prettyprint-func}
|
||
|
|
||
|
These code blocks show the same package `prettyprint', once in idiomatic Go and once
|
||
|
in functional Go. What can be seen is that the \mintinline{go}|for| loops have been replaced
|
||
|
by the usage of `foldl' and anonymous functions.
|
||
|
|
||
|
\begin{code}
|
||
|
\gofile{../work/funcheck/prettyprint/prettyprint.go}%
|
||
|
\caption{The original prettyprint implementation\autocite{prettyprint-orig}}
|
||
|
\end{code}
|
||
|
\begin{code}
|
||
|
\begin{gocode}
|
||
|
package prettyprint
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"go/ast"
|
||
|
"go/printer"
|
||
|
"go/token"
|
||
|
|
||
|
"golang.org/x/tools/go/analysis"
|
||
|
)
|
||
|
|
||
|
var Analyzer = &analysis.Analyzer{
|
||
|
Name: "prettyprint",
|
||
|
Doc: "prints positions",
|
||
|
Run: run,
|
||
|
}
|
||
|
|
||
|
type null struct{}
|
||
|
|
||
|
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)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func checkAssign(as *ast.AssignStmt, fset *token.FileSet) {
|
||
|
fmt.Printf("Assignment %q: %v\n", render(fset, as), as.Pos())
|
||
|
|
||
|
check := func(_ null, expr ast.Expr) (n null) {
|
||
|
ident, ok := expr.(*ast.Ident) // Lhs always is an "IdentifierList"
|
||
|
if !ok {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
fmt.Printf("\tIdent %q: %v\n", ident.String(), ident.Pos())
|
||
|
|
||
|
switch {
|
||
|
case ident.Name == "_":
|
||
|
fmt.Printf("\t\tBlank Identifier!\n")
|
||
|
case ident.Obj == nil:
|
||
|
fmt.Printf("\t\tDecl is not in the same file!\n")
|
||
|
default:
|
||
|
// make sure the declaration has a Pos func and get it
|
||
|
declPos := ident.Obj.Decl.(ast.Node).Pos()
|
||
|
fmt.Printf("\t\tDecl %q: %v\n", render(fset, ident.Obj.Decl), declPos)
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
_ = foldl(check, null{}, as.Lhs)
|
||
|
}
|
||
|
|
||
|
func run(pass *analysis.Pass) (interface{}, error) {
|
||
|
inspect := func(_ null, file *ast.File) (n null) {
|
||
|
ast.Inspect(file, func(n ast.Node) bool {
|
||
|
switch as := n.(type) {
|
||
|
case *ast.DeclStmt:
|
||
|
checkDecl(as, pass.Fset)
|
||
|
case *ast.AssignStmt:
|
||
|
checkAssign(as, pass.Fset)
|
||
|
}
|
||
|
return true
|
||
|
})
|
||
|
return
|
||
|
}
|
||
|
_ = foldl(inspect, null{}, pass.Files)
|
||
|
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
// render returns the pretty-print of the given node
|
||
|
func render(fset *token.FileSet, x interface{}) string {
|
||
|
var buf bytes.Buffer
|
||
|
if err := printer.Fprint(&buf, fset, x); err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
return buf.String()
|
||
|
}
|
||
|
\end{gocode}
|
||
|
\caption{The refactored, functional prettyprint implementation\autocite{prettyprint-functional}}
|
||
|
\end{code}
|
||
|
|
||
|
\section{Imitating Sum types in Go}\label{appendix:sum-types}
|
||
|
|
||
|
\begin{code}
|
||
|
\gofile{../work/examples/sumtypes/main.go}%
|
||
|
\caption{Demonstration of how sum types can be imitated with interfaces}
|
||
|
\end{code}
|
||
|
|
||
|
\section{Compiling and using functional Go}\label{appendix:install-fgo}
|
||
|
|
||
|
To compile and use the changes to the Go compiler that have been implemented in
|
||
|
this thesis, these instructions should be followed.
|
||
|
|
||
|
First, check out the Go source code:
|
||
|
|
||
|
\begin{bashcode}
|
||
|
$> git clone https://github.com/tommyknows/go.git && cd go
|
||
|
$> git checkout bachelor-thesis
|
||
|
\end{bashcode}
|
||
|
|
||
|
\subsection{With Docker}
|
||
|
|
||
|
If Docker is installed on your system, you can follow these steps from within
|
||
|
the checked out `go' git repository on the branch `bachelor-thesis'.
|
||
|
The downside of this approach is that it complicates building and sharing binaries.
|
||
|
To compile your own project, the directory has to be mounted into the container.
|
||
|
If your Guest OS is not Linux, cross-compilation is required so that the executable
|
||
|
can be ran on the host.
|
||
|
|
||
|
These steps have been wrapped inside a script that prints out the necessary commands
|
||
|
to configure your environment.
|
||
|
|
||
|
\begin{bashcode}
|
||
|
$> eval "$(./setup-docker.sh)"
|
||
|
\end{bashcode}
|
||
|
The commands within the shell script will
|
||
|
build Go in the container and print commands to configure the environment. The `eval'
|
||
|
command then executes these printed commands. Executing this command may take a while,
|
||
|
as the Go compiler is compiled within this process.
|
||
|
|
||
|
To build projects with the functional Go installation, simply use `fgo' on the command line.
|
||
|
An alias has been created that mounts the current directory and executes the `fgo'
|
||
|
command within the container.
|
||
|
|
||
|
Note that if this only configures the `fgo' command in the current shell session. To
|
||
|
persist it across shell-sessions, execute the script without eval:
|
||
|
\begin{bashcode}
|
||
|
$> ./setup-docker.sh
|
||
|
\end{bashcode}
|
||
|
|
||
|
And add the printed commands to your `.bashrc' (or equivalent). Further, you may
|
||
|
also need to change the binary path from `/tmp/fgo/bin' to a path which is not
|
||
|
cleaned up regularly.
|
||
|
|
||
|
\subsection{With a working Go installation}
|
||
|
|
||
|
If you already have a working Go installation on your system, the following
|
||
|
steps provide a way to get functional Go up and running in the same way
|
||
|
a normal go installation does.
|
||
|
|
||
|
These steps need to be executed from within the checked out `go' git
|
||
|
repository on the branch `bachelor-thesis'.
|
||
|
|
||
|
Build the functional Go binary and configure the environment:
|
||
|
\begin{bashcode}
|
||
|
$> cd ./src
|
||
|
$> ./make.bash
|
||
|
$> ln -s $(realpath $(pwd)/../bin/go) /usr/local/bin/fgo
|
||
|
$> go env -w GOROOT=$(realpath $(pwd)/..)
|
||
|
\end{bashcode}
|
||
|
|
||
|
The `go env' command sets the GOROOT to point to the newly compiled tools
|
||
|
and source code and is valid for the current shell session only.
|
||
|
|
||
|
\subsection{Using the installation}
|
||
|
|
||
|
After these steps, the binary (or alias) `fgo' can be used to test and build
|
||
|
functional Go code. `fgo' is not different to the normal `go' command, so
|
||
|
all commands that work with the normal `go' command also work with
|
||
|
the `fgo' command.
|
||
|
|
||
|
\begin{bashcode}
|
||
|
$> cd <code directory>
|
||
|
$> fgo test ./...
|
||
|
$> fgo build ./...
|
||
|
\end{bashcode}
|
||
|
|
||
|
|
||
|
\section{Building Funcheck}\label{appendix:build-funcheck}
|
||
|
|
||
|
Funcheck needs to be built against functional Go to properly detect the builtin functions.
|
||
|
If you have not done so already, install `fgo' as shown in Appendix~\ref{appendix:install-fgo}.
|
||
|
|
||
|
Then, funcheck can be installed directly with `go get' (or rather, `fgo get'). `go get'
|
||
|
downloads the source code to the Go modules directory (usually in \mintinline{bash}|$GOPATH/pkg/mod|),
|
||
|
compiles the specified package and moves the binary to \mintinline{bash}|$GOPATH/bin|.
|
||
|
|
||
|
\begin{bashcode}
|
||
|
$> # go get should not be called from within a go module
|
||
|
$> cd /tmp
|
||
|
$> fgo get github.com/tommyknows/funcheck
|
||
|
$> funcheck -h
|
||
|
\end{bashcode}
|
||
|
|
||
|
This installs `funcheck' into \mintinline{bash}|$GOBIN| or, if \mintinline{bash}|$GOBIN|
|
||
|
is not set, into \mintinline{bash}|$GOPATH/bin|.
|
||
|
|
||
|
You can also clone the git repository and use `fgo build' to build `funcheck':
|
||
|
\begin{bashcode}
|
||
|
$> git clone https://github.com/tommyknows/funcheck.git
|
||
|
$> cd funcheck
|
||
|
$> fgo build .
|
||
|
$> mv ./funcheck /usr/local/bin/funcheck
|
||
|
$> funcheck -h
|
||
|
\end{bashcode}
|
||
|
|
||
|
To run funcheck against the current directory / package, simply run
|
||
|
\begin{bashcode}
|
||
|
$> funcheck .
|
||
|
\end{bashcode}
|
||
|
|
||
|
\section{Building Gopls}\label{appendix:build-gopls}
|
||
|
|
||
|
Gopls is the official language server for Go. Similar to funcheck, there are two options
|
||
|
to install it on your local machine.
|
||
|
|
||
|
Installing with `go get':
|
||
|
\begin{bashcode}
|
||
|
$> # go get should not be called from within a go module
|
||
|
$> cd /tmp
|
||
|
$> fgo get golang.org/x/tools/gopls
|
||
|
$> gopls -h
|
||
|
\end{bashcode}
|
||
|
|
||
|
Or by downloading the source manually:
|
||
|
\begin{bashcode}
|
||
|
$> git clone https://github.com/golang/tools.git
|
||
|
$> cd ./tools/gopls
|
||
|
$> fgo build .
|
||
|
$> mv ./gopls /usr/local/bin/gopls
|
||
|
$> gopls -h
|
||
|
\end{bashcode}
|
||
|
|
||
|
% generate a chapter without the chapter heading
|
||
|
\clearpage
|
||
|
\phantomsection
|
||
|
\addtocounter{section}{1}
|
||
|
\addcontentsline{toc}{section}{%
|
||
|
\protect\numberline{\thesection}%
|
||
|
Assignment}
|
||
|
\includepdf[pages=-]{assignment.pdf}
|