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.
320 lines
12 KiB
320 lines
12 KiB
\subsection{Choosing the functions}
|
|
|
|
As mentioned in the introduction, the goal is to make functional programming in Go easier.
|
|
To achieve this, the first task is to implement some helper functions for slices, similar to those that exist
|
|
for lists in Haskell. To decide on which functions will be implemented, popular
|
|
Haskell repositories on GitHub have been analysed. The popularity of repositories
|
|
was decided to be based on their number of stars. Out of all Haskell projects
|
|
on GitHub, the most popular are\autocite{github-popular-haskell}:
|
|
|
|
\begin{itemize}
|
|
\item Shellcheck (koalaman/shellcheck\autocite{github-shellcheck}): A static analysis tool for shell scripts
|
|
\item Pandoc (jgm/pandoc\autocite{github-pandoc}): A universal markup converter
|
|
\item Postgrest (PostgREST/postgrest\autocite{github-postgrest}): REST API for any Postgres database
|
|
\item Semantic (github/semantic\autocite{github-semantic}): Parsing, analyzing, and comparing source code across many languages
|
|
\item Purescript (purescript/purescript\autocite{github-purescript}): A strongly-typed language that compiles to JavaScript
|
|
\item Compiler (elm/compiler\autocite{github-elmcompiler}): Compiler for Elm, a functional language for reliable web apps
|
|
\item Haxl (facebook/haxl\autocite{github-haxl}): A Haskell library that simplifies access to remote data, such as databases or web-based services
|
|
\end{itemize}
|
|
|
|
In these repositories, the number of occurrences of popular list functions has
|
|
been counted. The analysis does not differentiate between different `kinds' of
|
|
functions. For example, `fold' includes all occurrences of \mintinline{haskell}|foldr|,
|
|
\mintinline{haskell}|foldl| and \mintinline{haskell}|foldl'|; `map' also includes
|
|
occurrences of \mintinline{haskell}|fmap|.
|
|
Further, the analysis has not been done with any kind of AST-parsing.
|
|
Rather, a simple `grep' has been used to find matches. This means that it is
|
|
likely to contain some mismatches, for example in code comments. All in all,
|
|
this analysis should only be an indicator of what functions are used most.
|
|
|
|
Running the analysis on the 7 repositories listed above, searching for a number
|
|
of pre-selected list functions, indicates that the most used functions are `:'
|
|
(cons), `map' and `fold', as shown in table~\ref{tab:occurrences-list-funcs}.
|
|
|
|
\begin{table}[htb]
|
|
\centering
|
|
\begin{tabular}{ll}
|
|
\toprule
|
|
`:' (cons) & 2912 \\
|
|
\midrule
|
|
map, fmap & 1873 \\
|
|
\midrule
|
|
foldr, foldl, foldl' & 303 \\
|
|
\midrule
|
|
filter & 262 \\
|
|
\midrule
|
|
reverse & 154 \\
|
|
\midrule
|
|
take & 108 \\
|
|
\midrule
|
|
drop & 81 \\
|
|
\midrule
|
|
maximum & 45 \\
|
|
\midrule
|
|
sum & 44 \\
|
|
\midrule
|
|
zip & 38 \\
|
|
\midrule
|
|
product & 15 \\
|
|
\midrule
|
|
minimum & 10 \\
|
|
\end{tabular}
|
|
\caption[Occurrences of list functions]{Occurrences of list functions\footnotemark}
|
|
\label{tab:occurrences-list-funcs}
|
|
\end{table}
|
|
|
|
\footnotetext{See Appendix~\ref{appendix:function-occurrences} for how these results have been achieved}
|
|
|
|
Based on this information, it has been decided to implement the map, cons, fold
|
|
and filter functions into the Go compiler.
|
|
|
|
\subsection{Map}
|
|
|
|
The most used function in Haskell is map. The table~\ref{tab:occurrences-list-funcs}
|
|
counts roughly 1250 occurrences of map, although around 600 of those are from `fmap'.
|
|
fmap is part of the Functor type class\footnote{type classes
|
|
in Haskell are similar to interfaces in imperative and object-oriented
|
|
languages}, which is described as `a type that can be mapped over'\autocite{functor-wiki}.
|
|
In general, a common analogy of the Functor type class is a box. A functor is like a box
|
|
where a value can be put into and taken out again. fmap processes and transforms the item
|
|
in that box. For lists, this process means iterating over and processing every item within that list.
|
|
For the `Maybe' type it means `unpacking' the concrete value and processing it, or if there is
|
|
no concrete value, returning `Nothing' instead.
|
|
|
|
The map function is exactly like fmap but only works on lists:
|
|
|
|
\begin{quote}
|
|
\[map\] returns a list constructed by applying a function (the first argument) to all
|
|
items in a list passed as the second argument\autocite{haskell-map}.
|
|
\end{quote}
|
|
|
|
Some usage examples of map can be seen at~\ref{code:haskell-map}.
|
|
|
|
\begin{listing}
|
|
\begin{haskellcode}
|
|
Prelude> :t map
|
|
map :: (a -> b) -> [a] -> [b]
|
|
Prelude> :t fmap
|
|
fmap :: Functor f => (a -> b) -> f a -> f b
|
|
Prelude> map (*3) [1,2,3]
|
|
[3,6,9]
|
|
Prelude> fmap (*3) [1,2,3]
|
|
[3,6,9]
|
|
Prelude> map (++ " world") ["hello","goodbye"]
|
|
["hello world","goodbye world"]
|
|
Prelude> map show [1,2,3]
|
|
["1","2","3"]
|
|
\end{haskellcode}
|
|
\caption{Example usage for map and fmap}\label{code:haskell-map}
|
|
\end{listing}
|
|
Due to missing polymorphism, map cannot be implemented easily in Go. While
|
|
a specific definition of map would be
|
|
\begin{gocode}
|
|
func map(f func(int) string, []int) []string
|
|
\end{gocode}
|
|
this definition would only hold true for the specific type combination \mintinline{go}|int|
|
|
and \mintinline{go}|string|. A more generic definition, similar to append,
|
|
would be:
|
|
\begin{gocode}
|
|
func map(f func(Type1) Type2, []Type1) []Type2
|
|
\end{gocode}
|
|
For this to work, the function has to be implemented as a built-in into the compiler.
|
|
|
|
As there is already a `map' token in the Go compiler (for the map data type),
|
|
the function will be called `fmap'. However, compared to Haskell's `fmap',
|
|
Go's `fmap' only works on slices. This is due
|
|
to the absence of an `functor'-like concept. Again, due to the absence of polymorphism,
|
|
this cannot realistically built into the language in this context.
|
|
|
|
Nonetheless, to avoid possible naming confusions, the `map' function in Go will
|
|
be called `fmap'.
|
|
|
|
In Go, the usage of `fmap' should result in making the program~\ref{code:fmap-usage-go}
|
|
behave as shown\footnote{Printf's first argument, the
|
|
verb `\%\#v', can be used to print the type (`\#') and the value (`v') of a
|
|
variable\autocite{fmt-godoc}.}.
|
|
|
|
\begin{listing}
|
|
\begin{gocode}
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
)
|
|
|
|
func main() {
|
|
fmt.Printf("%#v", fmap(strconv.Itoa, []int{1, 2, 3})) // []string{"1", "2", "3"}
|
|
}
|
|
\end{gocode}
|
|
\caption{Example usage of map in Go}\label{code:fmap-usage-go}
|
|
\end{listing}
|
|
\subsection{Cons}
|
|
|
|
The name cons has been introduced by LISP, where it describes a record structure
|
|
containing two components called the `car' (the `\textbf{c}ontents of the \textbf{a}ddress \textbf{r}egister')
|
|
and the `cdr' (`\textbf{c}ontent of \textbf{d}ecrement \textbf{r}egister').
|
|
Lists are built upon cons cells, where the `car' stores the element and `cdr' a
|
|
pointer to the next cell - the next element of the list.
|
|
This is why in Lisp, \mintinline{lisp}|(cons 1 (cons 2 (cons 3 (cons 4 nil))))| is equal to
|
|
\mintinline{lisp}|(list 1 2 3 4)|. This list is also visualised in picture~\ref{fig:cons}.
|
|
|
|
\begin{figure}[h!]
|
|
\includegraphics[width=\linewidth]{../img/cons.png}
|
|
\caption{Cons cells forming a list\autocite{cons-image-source}}
|
|
\label{fig:cons}
|
|
\end{figure}
|
|
|
|
The cons operator thus prepends an element to a list, effectively allocating a
|
|
variable that contains the newly added element and a pointer to the `old' list.
|
|
As a result, prepending to a list is computationally cheap, needing one allocation
|
|
and one update.
|
|
|
|
In Haskell, the `name' of the cons function is the `:' operator.
|
|
In Go, names for identifiers (which includes function names) underlie a simple
|
|
rule:
|
|
\begin{quote}
|
|
An identifier is a sequence of one or more letters and digits. The first
|
|
character in an identifier must be a letter.\autocite{spec-identifiers}
|
|
\end{quote}
|
|
|
|
This rule forbids a function to be named `:'. Instead, the function could be
|
|
named `cons'. However, Go already has a function to add to the end of a slice,
|
|
`append'. Thus, adding to the beginning of a slice will be named `prepend'.
|
|
Using prepend should be very similar to append. The behaviour of `prepend'
|
|
should be equal to using `append' with a slight workaround\footnote{This workaround
|
|
is to create a slice containing the prepended element, and expanding the
|
|
destination slice with `...', as \mintinline{go}|append| is a variadic function}:
|
|
|
|
\begin{listing}
|
|
\begin{gocode}
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
)
|
|
|
|
func main() {
|
|
fmt.Printf("%#v", append([]int{0}, []int{1,2,3}...)// []int{0, 1, 2, 3}
|
|
fmt.Printf("%#v", prepend(0, []int{1, 2, 3})) // []int{0, 1, 2, 3}
|
|
}
|
|
\end{gocode}
|
|
\caption{Example usage of prepend in go}\label{code:prepend-go}
|
|
\end{listing}
|
|
\subsection{Fold}\label{sec:fold}
|
|
|
|
Fold, sometimes also named `reduce' or `aggregate', is another higher-order function
|
|
that is very commonly used in functional programming.
|
|
|
|
\begin{quote}
|
|
fold refers to a family of higher-order functions that
|
|
analyze a recursive data structure and through use of a given
|
|
combining operation, recombine the results of recursively processing its
|
|
constituent parts, building up a return value.\autocite{fold-wiki}
|
|
\end{quote}
|
|
|
|
In other words, fold processes a list one by one and executes a `combining operation'
|
|
on every element, for example summing up a list of integers.
|
|
|
|
The family of fold functions in Haskell consist of three different implementations of
|
|
that definition: foldr, foldl and foldl'.
|
|
The difference between foldr and foldl is hinted at their function headers:
|
|
\begin{listing}
|
|
\begin{haskellcode}
|
|
Prelude Data.List> :t foldl
|
|
foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b
|
|
Prelude Data.List> :t foldr
|
|
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
|
|
\end{haskellcode}
|
|
\caption{Function headers of the fold functions}
|
|
\end{listing}
|
|
The argument with type `b' is passed as the first argument to the foldl
|
|
function, and as the second argument to foldr. As can be seen in the illustrations
|
|
of foldl and foldr in~\ref{fig:fold}, the evaluation order of the two functions
|
|
differ.
|
|
|
|
\begin{figure}[h!]
|
|
\centering
|
|
\includegraphics[scale=0.5]{../img/foldl.png}
|
|
\includegraphics[scale=0.5]{../img/foldr.png}
|
|
\caption{Folds illustrated\autocite{fold-wiki}}
|
|
\label{fig:fold}
|
|
\end{figure}
|
|
|
|
This is most obvious when using an example where the function is not associative:
|
|
|
|
\begin{listing}
|
|
\begin{haskellcode}
|
|
foldl (-) 0 [1..7]
|
|
((((((0 - 1) - 2) - 3) - 4) - 5) - 6) - 7 = -28
|
|
foldr (-) 0 [1..7]
|
|
1 - (2 - (3 - (4 - (5 - (6 - (7 - 0)))))) = 4
|
|
\end{haskellcode}
|
|
\caption{foldr and foldl execution order}\label{code:foldr-example}
|
|
\end{listing}
|
|
In foldl, the accumulator (`0') is added to the left end of the list (prepended),
|
|
while with foldr, the accumulator is added to the right end.
|
|
For associative functions (e.g. `+') this does not make a difference, it does
|
|
however for non-associative functions, as can be seen in the example~\ref{code:foldr-example}.
|
|
|
|
The difference between foldl and foldl' is more subtle:
|
|
\begin{quote}
|
|
foldl and foldl' are the same except for their strictness properties, so if both
|
|
return a result, it must be the same.\autocite{fold-types}
|
|
\end{quote}
|
|
|
|
The strictness property is only relevant if the function is lazy in its first argument.
|
|
If this is the case, behavioural differences can be seen because foldl builds up a so
|
|
called `execution path' (nesting the called functions), while foldl' executes these
|
|
functions while traversing it already. An example of this is illustrated in
|
|
Appendix~\ref{appendix:foldl-strictness}.
|
|
|
|
To keep things simpler, Go will only have its versions of foldl and foldr, which
|
|
will both be strict --- the Haskell counterparts would thus be foldr and foldl'.\footnote{
|
|
If the behaviour from the normal foldl function is required, a workaround can
|
|
be applied in the Go version. See Appendix~\ref{appendix:foldl-go}}
|
|
The usage of these fold-functions is equal to the Haskell versions, where foldl's
|
|
arguments are switched in order.
|
|
|
|
\begin{listing}
|
|
\begin{gocode}
|
|
package main
|
|
|
|
import "fmt"
|
|
|
|
func main() {
|
|
sub := func(x, y int) int { return x - y }
|
|
fmt.Printf("%v\n", foldr(sub, 100, []int{10, 20, 30})) // -80
|
|
fmt.Printf("%v\n", foldl(sub, 100, []int{10, 20, 30})) // 40
|
|
}
|
|
\end{gocode}
|
|
\caption{Example usage of foldr and foldl in go}\label{code:fold-go}
|
|
\end{listing}
|
|
\subsection{Filter}
|
|
|
|
The filter function is the conceptually simplest higher-order function.
|
|
It takes a list and filters out all elements that are not matching
|
|
a given predicate.
|
|
This predicate usually is a function that takes said element and returns
|
|
a boolean if it should be kept or filtered.
|
|
|
|
A simple example:
|
|
|
|
\begin{listing}
|
|
\begin{gocode}
|
|
package main
|
|
|
|
import "fmt"
|
|
|
|
func main() {
|
|
smallerThan5 := func(x int) bool {
|
|
return x < 5
|
|
}
|
|
|
|
fmt.Println(filter(smallerThan5, []int{1, 8, 5, 4, 7, 3})) // [1, 4, 3]
|
|
}
|
|
\end{gocode}
|
|
\caption{Example usage of filter in Go}\label{code:filter-go}
|
|
\end{listing}
|