% -*- mode: latex; coding: utf-8; TeX-master: ../thesis -*- % !TEX TS-program = pdflatexmk % !TEX encoding = UTF-8 Unicode % !TEX root = ../thesis.tex To learn functional programming without being introduced to a new syntax at the same time ensures that programmers can fully concentrate on functional concepts. Although Go already supported a functional programming style, the programmer may not have known if the code was purely functional or if there still were imperative constructs embedded. In the last chapters, functional purity has been defined as a law based on one simple rule: immutability. Immutability dictates that once assigned, a variable's value never changes. This in turn leads to function purity, which means that functions do not have side effects and their return value is solely influenced by the function's parameters. It has been shown that although purely functional languages like Haskell aim to be completely pure, this objective is difficult to accomplish. The reason for this are Input / Output actions; user input, network connections, randomness and time are all impure. Haskell wraps these impure functions in the IO monad, which is a way to work around the compiler's optimisations based on functional purity. In addition, although the IO monad does not make impure functions pure, it does serve as documentation to its users (`if the function has IO, it is impure') and guarantees a certain execution order. Go on the other hand does not have this issue, as the Go compiler does not optimise execution based on purity guarantees. Having a similar construct to the IO monad in Go would, as such, only serve documentation purposes. Because of this, the decision has been taken to ignore the impurity that is implied with IO actions. Apart from IO, to achieve functional purity, the global state of a program should not influence the return values of specific function. This ties into immutablitiy; if global state can not be mutated, it can also not influence or change the result value of a function. Based on these observations, a static code analysis tool has been developed that reports all reassignments of variables. In other words, it forbids the usage of the regular assignment operator (\mintinline{go}|=|), only allowing the short variable declarations (\mintinline{go}|:=|). However, the experienced Go developer may know that the \mintinline{go}|:=| operator can also reassign previously declared variables, implying that the solution to the problem is not as simple as forbidding the assignment operator. Further, there are many more edge cases that have been detected with careful testing: To recursively call function literals, they must be declared beforehand (before assigning the actual function to it) because of Go's scoping rules. Additionally, exceptions had to be made for the blank identifier (\mintinline{go}|_|) and variables that are declared outside of the current file. With all of this in place, an algorithm has been chosen that is based on the identifier's declaration position. In the \gls{ast} that is being checked, every identifier node has a field which contains the position of it's declaration. If this does not match the current identifier's position, the operation must be a reassignment. The resulting binary, called `funcheck', successfully reports such reassignments: \begin{gocode} s := "hello world" fmt.Println(s) s = "and goodbye" fmt.Println(s) \end{gocode} \begin{bashcode} $> funcheck . file.go:3:2: reassignment of s \end{bashcode} This linter can be used and executed against any Go package. To eliminate the reported errors, code has to be rewritten in a purely functional manner. However, functional code often relies heavily on lists and list-processing functions. Although Go does not have a built-in list datatype, Go's slices, an abstraction built on top of arrays, mitigate a lot of downsides when comparing regular arrays to lists\footnote{Arrays / Slices and Lists have a different runtime behaviour (indexing, adding or removing elements). However, the performance of the code was not considered to be in scope for this thesis.}. What Go's slices lack on the other hand are the typical higher-order functions like `map', `filter' or `reduce'. These are commonly used in functional programming and most languages contain implementations of these functions already --- Go does not. Due to the lack of polymorphism, writing implementations for these functions would result in a lot of duplicated code. To mitigate this issue, the most common higher-order functions have been added to the list of Go's built-in functions, which are registered, type-checked and implemented within the Go compiler. As these are handled directly at compile time, built-in functions may be polymorphic, for example allowing the programmer to use the same `filter' function for all list-types. To determine which higher-order functions are most commonly used, the most popular open-source Haskell projects (pandoc, shellcheck and purescript, to name a few) have been analysed. As a result, `fmap', `fold', `filter' and `prepend' (`cons') have been added as built-ins into the compiler. These functions make it easier to write purely functional code in Go, in turn helping the programmer to learn functional programming with a familiar language and syntax. While implementing these functions in a regular Go program would be a matter of minutes, adding them to the Go compiler is not as simple. To illustrate, the functions have been written out in regular Go in the chapters~\ref{ch:impl-fmap} to~\ref{ch:impl-filter} and are 33 lines of code, all functions combined. In the Go compiler, it is necessary to register the functions, type-check the calls and manipulate the \gls{ast} instead of writing the algorithm in Go code directly. This took more than 800 lines of code to do so. As a result, using these functions is equal to using any other built-in function: there is documentation in Godoc, type-checking support in the language server\footnote{If the language server (gopls) is compiled against the modified version of Go, as described in Appendix~\ref{appendix:build-gopls}} and in the compilation phase, as well as a polymorphic function header, allowing the programmer to call the function with any allowed type. Demonstrations of these functions and how functional Go code looks like can be seen in Chapter~\ref{ch:application}. With these additions to Go and its ecosystem, aspiring functional programmers can fully concentrate of the concepts of functional programming while keeping a familiar syntax at hand. However, it should not be considered a fully featured functional programming language. Rather, it should serve as a starting point and make the transition to a language like Haskell easier. Differences in the syntax between Haskell and Go exemplify why purely functional programming languages have a distinct syntax compared to imperative or object-oriented languages. Many constructs can be expressed more concisely in Haskell, without the additional overhead that many programming languages, including Go, introduce. Using `the right tool for the job' is important, and this paper shows that imperative or object-oriented programming languages are not the right tool for production-grade functional programming. However, they can serve as a good starting point and help transitioning to a pure functional programming language.