Comparing Go and F#

jkone27
7 min readMar 29, 2021

--

While reading learn go in y minutes some time ago, I noticed some common «concepts» in the language design of go-lang and F#, a language for .NET I really love.

PS. a really nice comparison summary also here from a form thread on reddit.

✔️Here for reference if you want to play around with both languages:

Important Notice: I am not a Go programmer myself, but an F# enthusiast, so feel free to correct my imprecisions or mistakes :)

Note: I am not talking about performance here, but now-a-days NET5 (and upcoming NET6) are pretty well oiled runtimes, often seen competing with low level languages like go. You can find more on the topic googling by yourself if the performance is what interests you most, I must say it will not be the focus of this article.

Language Family Tree

First go is a C-like language whereas F# is a ML-like language (from Stanford meta language)

Go genealogy tree

And here instead the family tree of F#, part of the ML family, descending directly from OCaml and Stanford ML.

F# Genealogy tree

Note: I think in the latest diagram Go is shown as coming from C# mostly for garbage collection reasons, and type inference on left-hand expression assignments similar to the var keyword. Concurrency in C# is also somewhat different from Go, but maybe it has its similarities as well. Here is an article from Microsoft comparing C# and Go.

Programming Paradigms

✔️Both go and F# share the concept of type and separation between data and functions, between state and behaviour.

type person struct {
name string
age int
}

var p = person{age: 11, name: "cecilia"}
var x = p.age

F# allows for this separations, though it doesn’t mandate it, making it possible to extend types with functions as well as creating regular OO Classes and object references.

type Person = {
Name : string
Age : int
}

let p = { Age = 11; Name = "cecilia" } //Person
let x = p.Age //int

In Go on the other hand, is not possible to have classes with methods, sharing data and functions. This is probably a good default, even though it poses hard limits on the capabilities of structuring code.

The function is always declared externally, outside of the “data” context, and thus leads us to a deliberate “violation” the OO principle of encapsulation.

func add(x int, y int) int {
return x + y
}

Package techniques might be used to increase encapsulation but it’s more artificial than «native» OO Lang’s, probably in a similar manner as Modules in F# when using the pure functional (OCaml derived) programming paradigm.

let add x y = // auto type inference
x + y

Go is not fully OO but appears mostly procedural like C, and also not functional being quite non-declarative, making the allocation policy part of the language and using the concept of pointers for memory on the heap and struct for memory on the stack. It’s ok for low-level programming, but this makes me doubt its usage as a “high-level” language.

var pa *Student   // pa == nil
pa = new(Student) // pa == &Student{"", 0}
pa.Name = "Alice" // pa == &Student{"Alice", 0}

Example of “shape” (weak) inheritance in go

let’s define Stringer as an interface type with one method, String.

type Stringer interface {
String() string
}

let’s define pair as a struct with two fields, ints named x and y.

type pair struct {
x, y int
}

Let’s define a method on the type pair.

Pair now implements Stringer because Pair has defined all the methods in the interface. Is this true inheritance?

Not really, this is a bit more like a shape, so this is a runtime method dispatch, not really inheritance. Useful for code reuse but not for strong type checking…

func (p pair) String() string { 
// p is called the «receiver»
// Sprintf is another public function in package fmt.
// Dot syntax references fields of p.
return fmt.Sprintf(«(%d, %d)», p.x, p.y)
}

This means quite some things. First, I could not have empty interfaces (at least the way we used to think of them), e.g. if I want to lock down types for specific reasons in my implementation. In Go empty interfaces seem to do exactly the opposite of what they do in OO.

Secondly, I don’t need to know anything about my interface to implement it, so potentially my code using my interface type, can invoke other totally un-intended stuff on other types that declared a similar “shape”?

Here is how a similar code would look in F# (closer to the standard OO/C# way)

type IStringable = abstract member String : unit -> string

type Pair(x,y) = //inferred as int by the compiler
interface IStringable with
member this.String() = sprintf "(%d, %d)" x y

Nil, seriously? Go only

This also scares me quite a lot and confuses me of why Golang was designed without the biggest error of computer science in mind originating from the C programming language, or am I again missing something?

The zero value of an interface type is nil, which is represented as [nil, nil].Calling a method on a nil interface is a run-time error. However, it’s quite common to write methods that can handle a receiver value [nil, Type], where Type isn’t nil.You can use type assertions or type switches to access the dynamic type of an interface value.

I bet there are advantages here, but I tend to see more drawbacks (?).

F# only allows null for strict interoperability with .NET framework and libraries to be passed around in methods but does not allow null to be set explicitly (unless using explicit archaic keywords, see link above), making it a very safe language by default.

In addition, F# has an Option<T> type, containing Some(T) or None, allowing for type-safe optional objects navigation. Even in interop situations with other libraries, it’s always possible to adapt code not to use null.

// F# example from documentation, Pass a null value to a .NET method.
let ParseDateTime (str: string) =
let (success, res) =
DateTime.TryParse(str, null,
System.Globalization.DateTimeStyles.AssumeUniversal)
if success then
Some(res)
else
None

Similarities and Differences

✔️ Both Go and F# use _ for ignoring variables and have a similar tuple deconstruction concept (now also available in C# for a while too).

✔️ Concurrency is handled in F# with the beautiful async monad.
In Go this is taken care of with the concept of channels and go routines (running on a channel).
In C# similarly, we have async/await plus in general the underlying TPL.

Type inference in go is limited at the assignment level with := like in C# with var.

Instead F#, like its mother language OCaml, has a wonderful and powerful automatic generalized type inference: write like js or python, collect type annotations more than C# or Java inferred by the compiler for you! if you don’t believe me try it online.

Type inference in F# is just amazing, it’s more like dropping rays of sunlight on your code and letting the program infer the shadows and annotate it for you. Amazing to see in action.

✔️ Go favours the usage of railway-oriented programming ROP with ok, ko concept during function evaluation in a similar way, but less elegant, of the Result<TOk,TError> or either monad type in F#.

❌Go allows nil (null) and doesn’t have maybe/option monad like all ML Lang’s have (F#, OCaml, Haskell) and now also C#8 has in his own rights (? Nullable references).

Go doesn’t have generic types <T> for functions or types, so it does not allow static generic polymorphism, so it only enables dynamic dispatch via interfaces.

Go doesn’t have anything like type providers, nor C#, where F# is a leading language for rapid prototyping.

❌Go does not have a REPL (read evaluate and print loop) or an interactive programming environment, in a similar way to Python, for rapid prototyping and POC, whereas F# has it and it’s very much integrated also both in the IDEs and in the compiler itself.

🕸️Go uses parenthesis (like C, JS, C#, Java) for scoping whereas F# uses indentation (like Python but checked at compile time) like many other modern ML-family languages.

F# uses curly braces for computation expressions which is monadic computations, reserving just indentation for method scoping, making code shine for effectiveness and expressiveness.

let fetchAndDownload url = async {         
let! data = downloadData url
let processedData = processData data
return processedData
}

Conclusions

I don’t know much about Go, sure has its perks and advantages.
It indeed resembles a lot a low-level system programming language like C, especially the usage of pointers (which frankly looks a bit unhandy to me),

but completely rebuilt from the ground up, to make it less painful to the programmer.

✨So probably it has its own advantages and areas of application which make it shine mostly, as well as performance and memory advantages which make it for sure an amazing language to learn and to know.

✨ From what I understand what mostly shines in Go is its compiler, being ultra-fast and creating ultra-tiny binaries with self-contained runtime, able to run basically everywhere and with an impressively low memory footprint, thanks also to an amazing garbage collector.

Garbage collection, channels for simplified concurrency and so on seem very nice features improving a lot on low-level programming languages.

--

--

No responses yet