We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Functional Programming with Go

00:00

Formal Metadata

Title
Functional Programming with Go
Title of Series
Number of Parts
490
Author
License
CC Attribution 2.0 Belgium:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor.
Identifiers
Publisher
Release Date
Language

Content Metadata

Subject Area
Genre
Abstract
Are you tired of seeing Object Oriented code everywhere, with mutations, side-effects and headaches? Luckily, writing Go does not have to be that way! Functional programming is perfectly possible within Go, hence we can leverage FP techniques to make our code more robust, testable and fun. Functional Programming in Go Go is a multi-paradigm language, yet most code you encounter ‘in the wild’ is written in a mostly Object-Oriented way. Yet, Go allows us to write code in a functional way as well, which offers certain advantages over a more traditional “OO” approach. What follows in this description is also the general flow of how it will be presented. What is FP? First we’ll start by defining what we mean by functional programming. Superficially Go might not look like what you expect a functional language to look like. But we’re really just missing the syntactic sugar here, as a lot of the underlying concepts that are central to Functional Programming are reflected in Go. Hence it is important to take a look at what many programmers consider requirements for being “functional” such as: Higher-order functions, recursion (with tail-call optimization), purity, idempotence, .. and how these requirements are met (or aren’t met) by Go. How to leverage them in Go Once we have convinced ourselves that Go gives us the building blocks for writing FP code, we’ll dive into some concrete examples of what we can do with Go. We’ll look at function currying, continuation-passing style programming, pure functions, recursion (without TCO), monads and more. It’s important to highlight here why we want to use these constructions and when. At best case, you’ll learn how to leverage them in your own codebase. At worst, you’ll have seen some cool things with Go. Don’t be put of if you don’t know these terms yet, we’ll start with the easy concepts you’ve probable already used such as recursion, and work our way up to the complexer ones. Using libraries Go actually has libraries that provide an API for programming in a more functional style. We’ll give them an honorable mention but they won’t be the focus of the talk, as you can get started easily without them. But, they do offer certain things we “miss” in Go by default (like Generics). Benefits Writing Go code in this style has numerous benefits over our traditional approach. My goal of this talk is not just to show you cool things you can do with Go, but also why you want to them. You’ll also see introducing them to an existing codebase is easy, and that FP is really not as scary as it might sound! Downsides Using this style of programming is not entirely a walk in the park. There’s a price to be paid for writing functional code in Go, the main one will be that you’ll take a performance hit. But the performance hit might not be where you expect it! Functional programming is one tool in your toolbox, it’ll greatly empower you to solve certain problems, whilst it’ll help you shoot yourself in the foot for other problems. Bonus benefits! Yes, you’ll even take away something from this talk you might not have expected! A lot of people think of Haskell when they hear functional programming. Which might have scared them away from functional programming. In this talk you’ll get a look at functional programming with a familiar syntax and a language you already love. This will help you understand the underlying concepts and see how they relate to Haskell and other functional languages, where the syntax might be a bit different but the idea remains the same. Do I need prior knowledge of FP? No, absolutely not! You don’t need to have done functional programming before to benefit from this talk. There are concepts for all levels of understanding of functional programming. If you don’t know anything about functional programming yet, you’ll discover it in this talk. And if you’re already an FP-wizard who dreams in Haskell, you’ll learn how to transfer that understanding to Go.
Functional programmingProgramming languageFunctional programmingRoundness (object)Computer animation
Menu (computing)outputFunctional programmingOpen sourceProgramming languageDescriptive statisticsOrientation (vector space)Library (computing)Self-organizationObject (grammar)Physical lawMultiplicationComputer animation
Interior (topology)WordLine (geometry)Social classBitMathematicsElectronic mailing listProgramming languageObject (grammar)Integrated development environmentResultantIndependence (probability theory)NumberFunctional programmingCodeSummierbarkeitPattern languageState of matterFunctional programmingCondition numberDeclarative programmingJava appletPhysical lawConcurrency (computer science)Video gameCycle (graph theory)FamilyComputer animation
Function (mathematics)String (computer science)Beer steinRing (mathematics)Normed vector spaceGame controllerComputer programmingFunctional programmingFunctional programmingOrder (biology)CASE <Informatik>Sound effectMultiplication signHydraulic jumpString (computer science)2 (number)DatabaseRight angleOperator (mathematics)Bit rateCodeDifferent (Kate Ryan album)SpeicherbereinigungPoint (geometry)Arithmetic meanGroup actionMereologyFunction (mathematics)QuicksortResultantGame theoryElement (mathematics)Pointer (computer programming)Object (grammar)Revision controlProcess (computing)Program slicingProgramming languageLine (geometry)Electronic mailing listPlanningPredicate (grammar)NumberMusical ensembleSubject indexingState of matterSystem callChainLogicInstance (computer science)Execution unitJava appletSoftware testingLoop (music)Lambda calculusMathematicsStack (abstract data type)TimestampDeclarative programmingCore dumpSinc functionBoolean algebraRandomizationoutputRow (database)Computer animation
Algebraic closureString (computer science)Hill differential equationReduction of orderConvex hullLetterpress printingDeclarative programmingComputer configurationMaxima and minimaFunctional programmingData structurePoint (geometry)Server (computing)Survival analysisConnected spaceQuicksortNumberHoaxMultiplication signService (economics)ArmStudent's t-testPredicate (grammar)Type theoryVariable (mathematics)Parameter (computer programming)Process (computing)Default (computer science)Hardy spaceBit rateCASE <Informatik>Electronic mailing listConstructor (object-oriented programming)Line (geometry)ResultantSimilarity (geometry)PrototypeProtein foldingAlgebraic closureStress (mechanics)Physical systemProgramming languageGame theoryTerm (mathematics)Different (Kate Ryan album)BitDemosceneRight angleSoftware developerInheritance (object-oriented programming)Letterpress printingFunction (mathematics)Order (biology)Revision controlBlock (periodic table)BuildingIntegerDeclarative programmingString (computer science)Query languagePointer (computer programming)Drop (liquid)Functional programmingInternet service providerIntegrated development environmentLoop (music)FrequencyLecture/Conference
Functional programmingSoftware bugLogicType theorySinc functionLevel (video gaming)WritingGroup actionPoint (geometry)Field (computer science)Computer animation
Menu (computing)MaizeHill differential equationFaculty (division)40 (number)Moment (mathematics)ProgrammschleifeComputer programmingNumberRoundness (object)Functional programmingLibrary (computing)Type theoryArmFunctional programmingRange (statistics)Filter <Stochastik>Metropolitan area networkForcing (mathematics)WeightMereologyStandard deviationOperator (mathematics)Multiplication signGenerating functionCore dumpPoint (geometry)Physical systemBitProgramming languageData structureElectric generatorSinc functionMassProgram slicingJava appletString (computer science)Reflection (mathematics)SummierbarkeitReduction of orderArrow of timeCASE <Informatik>CodeIntegerDefault (computer science)Pointer (computer programming)Lambda calculusAbsolute valueComputer file
Ring (mathematics)Condition numberRight angleAddress spaceOperator (mathematics)Functional programmingThree-valued logicGoodness of fitLecture/Conference
Point cloudFacebookOpen sourceDiscrete element methodLecture/Conference
Transcript: English(auto-generated)
Cool, so next up, we had someone that flew all the way from, I think, West Brussels. Yeah, so we have a Belgian person, which is nice. He's going to be talking about functional programming in Go.
Round of applause for him, please. Thank you. Can everybody hear me okay? Yep, okay, great. So I'm Dylan. I'm going to be talking about functional programming in Go. And so, when you look at these languages, one does not look like the others.
Most of them are obviously functional languages, and Go is actually kind of an outlier here. Well, it kind of looks like that when you look at a lot of open source libraries out there. Most of them are programmed in the same kind of pseudo-object oriented way.
But if you look at Go, the description is, it's a multi-paradigm language. You don't really have to stick to using objects everywhere. So when people think of what is a functional language, quite a few things come to mind. So you can have a declarative language, right? So in a functional language, you tend to say what you want, but not how you want
to get it. So in Haskell, you would say sum, and then a list of numbers, and it will return the sum of the numbers. While in C, you would iterate over the list and basically keep the sum in a variable and keep updating it, and then return the result at the end. Of course you want first class functions, which Go completely supports.
Often pattern matching, that kind of goes hand in hand with recursion, because languages tend to be very heavily on recursive side. You tend to have pure functions, so we'll go into that a little bit more. And immutable functions, so you have your state, and your state of your objects doesn't change throughout the lifecycle.
You create new objects instead of modifying one, and actually many, many more things. A scary word is monads, that's something that people relate to functional programming. But the question then is, why would you want this? Why do you want to do this in Go? And well, you create safer programs, you have easier concurrency, right?
You have no shared states, so you can run things in parallel without really influencing each other. You're going to have less code, but be more expressive. So normally I would say like having just one line of code instead of ten lines of code could be a bad idea if you're doing something traditional in Java, but yeah, if you read Haskell, that one line, just ten lines of work, but it's very, very expressive
and you can understand it, so that's an advantage. They tend to be easier to debug and easier to test. Everything can run independently. And yeah, it just makes using such a language kind of a pleasure to use. One of the complaints of this language is they don't have an IDE very often, but
well, the tools are good enough in the language that you don't really need them that often. So that's what you're going to get out of functional programming. So let's jump into some examples. This talk will mostly focus on how to use the functions and higher order functions. So you have pure functions.
So the basic idea is your function will not produce a side effect, right? That makes them more testable and you can run them concurrently. So in a traditional example, you have A, you run a function lambda, and you get B as a result. That's how every function works in a functional language.
In Go, you could actually have a function A, so A goes to a function lambda and it calls change name. And suddenly that object A actually has a different name, but it's not a new object. So you have now introduced a state that is modifiable. So in code, a simple pure function would look like this.
You have greet, hello, it takes a variable, it returns hello and then the name of the person. It will always do the same thing and it's safe. In a bad example, you want to count how often this function is called. And call counter is modified in the function. I mean, there's more wrong with this code because it's a global in this case.
And so now if you run this function many, many times, well, what will call counter be? Depends how often you run it. If it runs in parallel, it depends how far in the execution the program is, right? So different function calls might be at different steps of execution and you can't know what your call counter will be when your function has exited.
So in a more Go sense, so if we remove the global, we can still get this kind of behavior by having a rename method in this case. And so we're using a pointer now to a person, so the method receiver. And we modify it. Now luckily, this function is called rename, so you expect the name to be changed. But any function that receives a method pointer, you don't know how it will change your object.
If it's 10 lines of code that says do something, well, what parts of your object are changed? You don't really know at this point, right? But if you call a function like this, which takes a value instead of a pointer, you know that whatever happens, your original object is still intact. You still have it.
So the calling function has two copies. It has the original version and it has a function, well, a return value from the rename. And so this kind of gets the reaction quite often, like, okay, but now I'm using values everywhere and performance is gone. As a side note, that's just not the case in Go. Values are faster than pointers in many, many cases.
There's places where you want to use pointers, but pointers live on the heap, generally, not always. And that's garbage-collective, and that is slower. So if you use values, you're constantly on the stack, and stack operations are push and pop and they're quite fast.
So item-pointing functions. So functions should always produce the same results for the same input. So X goes in, Y goes out, it's always the same for every X, the same Y comes out. And you're going to violate this somewhere, because for some programs to be useful, you don't want this.
If you're programming a game, part of it is that you need some kind of randomized function. Something needs to happen randomly. Well, you don't know what the output will be because, well, it's random. So a simple item-pointing function is uppercase. Every time you get a lowercase input, the uppercase will be the same. So now if you have this function, which basically, it says set the update time.
So you create a row in a database somewhere, and you want to keep the timestamp of when this happens, and you do it in Go. You call time.now. If you call the function now, or two seconds later, all the result is going to be different. So you can't test this anymore.
A way around it is by basically re-upping this in another function that takes the time as a variable as well. So now they said, update time could be unit-tested, because you provide the time and you know what the output will be on the send time. Of course, the calling function will still call time.now, but you've basically managed to contain most of the logic in an item-pointing and a pure function, and the calling function
will need to do something messy. But the core functions can be tested. And so the idea would be that your whole program consists of these small testable functions, and then you chain them together in ways that you need them. So first-class functions, or higher-order functions, like people who come from other
languages like Java and stuff, they're starting to do this as well. So you have a slice of strings, and you take basically a predicate function that tells you if your string satisfies the predicate, yes or no, it returns a simple boolean. And if it satisfies the predicate, your output list will basically append it, and then you return the result of every element that matches your filter method.
So you can create a function called long-strings, which returns true if the string is longer than 10. You call this in the main function, and basically you've written out a higher-order function because you passed your long-strings function to the filter function that is using this.
If you would imagine that the left part does not exist, that would be declarative programming on the right. So the main function is saying filter by x, and it's not telling you how to filter. So the loop is abstracted away, basically. And if we continue with higher-order functions, we can also use closures.
And that is basically using a function inside a function, and the inner function is referencing the outer function variables. So, this aptly named function, closure, has a function inside of it called drop. But the drop function references a variable that's not from itself, right? So the drop references the string from the closure, s, and then basically drops x amount
of characters. And then we call the drop function with 5. That's the basic idea of a closure. You're going to basically use variables that are not from your own function, but from a parent function. So yeah, we can use this, and then we drop hello from hello world, so we just have world
left. And then this comes up, we can take one step further again, and then we can get into function querying. And function querying is kind of the same idea, but instead of using the function directly, you're going to return a function. And in the end, in functional languages, you tend to basically have every function takes
one parameter, and that function returns a new function requesting the following parameter. And that's how you chain all your functions together. Now, in actual functional languages, this is done basically behind the scenes, so you don't notice, right? You just write it with five parameters, but it will convert it in the background to five different functions taking one parameter.
In Go, we don't really have this luxury, so we have to write this ourselves, which makes it a bit more verbose. But you have a greet function that takes a prefix and a name, and it basically is going to print, for example, hello, and then your name. And then we create a prefix greet function, and this function only takes the prefix, but
it's going to return another function that will take the name as a parameter and return a string. And so inside the prefix greet function, we are returning another function, right? Taking the string, returning a string, and it's only when this inner function is going to be evaluated that we're going to call the greet function with the prefix and the name.
All right? So this allows us to basically start creating building blocks, and we're going to create intermediate functions and store them as variables. So then we can create a hola and a hello function, right? Hola has the prefix hola, and hello has the hello prefix, and they're sorting variables.
At this point, they're not really evaluated to a result yet. They're only evaluated to a partial result, to another function. And if you would call this function hola and hello, we false them, then we create two different hola and hello print lines. So basically, this allows you to compose functions in a different way, and to store
them somewhere as a variable in your struct or whatever. And there's actually a practical use case for doing this type of stuff, right? It's not just fun to play around with, but imagine that you have a function taking quite a few parameters. Typically, it's going to be a constructor of some kind, and in typical Go fashion, in
the top example, I've given them one letter variable names, right? Because in Go, everything tends to be concise. And then your IDE will try to help you, and it says to you, okay, I want nlpam, and it also gives you the type. Well, you don't really know what this is, right?
If you have a more Java mindset, you give them long names, or extremely long names if you want to. And then you have name, last name, foreign, age, and married. So the bottom example, you kind of know what it does, but you're relying on your IDE. So let's make such a constructor.
So we have a type server, and instead of listing the options in the server itself, we're creating an older struct with the options in it. So we're going to store the maximum number of connections, the transport type, and a timeout. And so we're also going to create a type alias, so a type definition, of server option. And server option is a function that takes options and returns new options.
This is just the same as if you would actually create a pointer to options and modify options in place, but as we've seen earlier, we're trying to avoid that kind of modifying stuff. So we're returning new options every time that function is called. And then we have the constructor, and it takes the variadic server option as input,
so you can pass as many as you want. And so now we would create the closure slash period function. So we have maximum number of connections, which is a function you can call with an integer. And that returns one of those functions that can modify options. So it returns a function of options to options.
And it's only when this function is evaluated that we're going to set the maximum number of connections equal to the integer that's coming in from the outer function. So this is the same closure type stuff that we saw earlier. Our inner function is referencing the outer function parameter, and we're returning the function instead of actually evaluating it. We can do the same for timeouts, or for transport layers, or for however many things we want.
And in our constructor, we're simply going to say, okay, I have all these server options, I'm going to loop over all of them, and I'm going to call the options function that we're getting. So maximum number of connections or timeouts function. And those are going to modify our options in place.
And at the end, we're actually going to create a server with the options that come in from the outside. So when we do this, so we have a new server now, we can just provide as many of the options as we need. We don't need to put, like, if we have a list of 10 items in the constructor, we know we only need to set two things, we just pass two of them,
and the order doesn't matter anymore, right? Just doesn't matter, they're all server option functions. And then you get the output. And okay, we didn't say the timeout, so the default zero value is zero. And actually, this is a really nice way of one having, like, optional parameters in your Go functions or constructors, but you could also provide default values if somebody doesn't enter something.
So in this example, if options had not been initialized as the empty struct, we could have put, let's say, timeout 3000 in here. And if a timeout function is not passed to the constructor, timeout will never be overridden. So that's a way to get basically default values in your functions and in your constructors,
which Go typically does not offer. So that's a practical use case of this kind of closure slash querying stuff. Another side of functional programming is that things need to be declarative, right? We want to basically just say, hey, this is our list of things that we want to do,
and the language will figure out how to do it for us, basically. It's doing it on the background. So remember our earlier example? It was basically the same stuff as this. We have a filter function, works on users this time, and it's basically gonna say, okay, if the user satisfies our predicate,
we're gonna add the user to the output and then return it. And then we could call this, and we could have, like, okay, we want to have all students who are above the age of 21, whatever, and we want to return a list of these students. Well, that's nice because it's declarative programming. And then you realize, hey, we're writing Go, right?
There is no generics, and there are no contracts as of yet. So that means that this filter function works for one type. I have users, but I also have cars. Okay, yeah, it doesn't really work for cars, so I create a function again for cars and for all my other types. That's gonna be quite hard to maintain. At some point, you're basically gonna realize, okay, I did make a bug in the filter function,
and I have to fix it in 20 places, which is kind of unfortunate. And you're also never gonna be entirely declarative, right? You're just hiding the loop, we're putting it somewhere in a filter function, or in a map or a reduce function, but we still have to implement the logic ourselves.
And there's actually quite a few libraries that aim to make Go more functional. So one of them is Gleam. And that's using Reflection to kind of introduce these filter functions for any type. So it's using Reflection to figure out what type it is, and then to apply the appropriate methods. And then there is Pi and Haskell.
And basically, so Pi works on slices, right? And they generate methods using the Go generate tools. So they generate filter methods and reduce methods for any type. And you run the code generator, you specify what type you want to run it for, any type in your system, and it will create a file with all those functions entered for you.
And so Elliot made that, it's a really nice thing, and I basically stole that idea, and I made Haskell, and I said, okay, but I only want Haskell functions that do this stuff. So you can create any type of function Haskell has for Go, and you can just generate it with some other useful functions.
So this is a library that enables functional programming in Go. It makes it a little bit easier. So you generate functions, it's declarative, it's pure, it's nil-safe, like most functional languages, they don't deal with the concept of nil in the same way. This library, if you pass it as nil, it will basically assume it's a default zero, and it will still work.
So Go also helps here, of course. And everything is immutable. So throughout the pipeline of filters, mapping, reducing, your data is never destroyed. You're always working on a copy, basically. And then you can use this kind of stuff in Go. So you say, I want a range of integers from minus ten to ten.
It's going to give you all the numbers, give me the absolute values of those numbers, filter them by an isEven function that you have defined somewhere, and then return the sum. So you don't have to write any loops, and this basically will work magically for you. At the moment, we can generate about, like, 40 to 50 Haskell functions for any type that you provide.
So you basically hit any struct, type T struct, and we can generate all these functions for you, and we check that, you know, if you're requesting string operations, that your struct is actually of a type string, or that we can work with it. So, in conclusion, yeah, functional programming is perfectly possible in Go.
Actually, it works well, but there is no syntactic sugar. So, in most languages, like in Java, they introduce the arrow lambda operator, which makes things a bit more readable. Well, you have lambdas in Go, right? But you don't have the syntax for them.
So that's kind of a downside, I guess. It makes the code more readable in a way, more easy to understand, but also very, very robust at some point. But it can be quite useful, depending on your use case. And, well, you should kind of use some libraries if you really want to be completely declarative, for example, because otherwise you're going to have a lot of fun maintaining a million functions for a million structs.
So, yeah, that's my takeaway. So, thank you. If you want to follow me, basically, that ad tag everywhere where I'm available, it's the same tag, so it's that name. And the library is also on GitHub, so you can play around with it.
So, thank you. So, we have some time for questions. So, are there any questions? Raise your hand. Okay. I'm going to go in priority of who's closer, so I'll go to you up there in a second. There was a hand here.
Hey, thanks for the talk. Very interesting. Maybe just a remark. There are certain types in Go, like even if you pass them by value, they're always passed by reference, like a map. So, if you pass a map, you can't rely on the fact that it's not been modified.
Yeah, yeah. Repeat the question first, and then answer. What is the concrete question about it, though? That some values are not passed by reference. I was just saying, you can't rely on the fact that something is passed by value, because some things are always passed by reference, like a map.
So, even though it looks like it's passed by value, it can be modified by the whole function. Yeah, yeah. Yeah, that's true. So, you can still modify them.
Do you have a way around the lack of the conditional operator? Because writing functional programs without that seems difficult to me, but it can be hard. So, did you hear the question? No, I can't. The question is, and please, people who are just leaving, try to be a little bit more quiet. So, the question is, how do you deal with the lack of ternary conditional operator in Go for functional programming?
Yeah, the fact that you don't have a ternary operator in Go. Well, I don't really miss it, to be honest. It's a matter of being used to it, I think. You probably come from Java or something, right?
Now you're used to using it, but in the end, it's just basically branching, so it doesn't really matter if you use the operator or not. Quick question, where is Maya? That's our next speaker, Maya Rasheesh. Okay, good. Any other questions? Okay, so thank you very much.
We'll start the next talk at ten.