Functional Programming
Table of Contents
- Introduction.
- Purpose.
- Pure Functions.
- Immutability.
1. Introduction.
Functional Programming — abbreviated as FP — is a way of composing code with pure functions, and thus avoiding mutable-data, side effects and shared state. Functional programming is a declarative programming paradigm where logic is expressed without explicitly describing the flow control. State flows through pure functions, in contrast with object oriented programming, where state is usually shared and tied with methods in objects.
2. Purpose.
Programming is intrinsically hard, so we need tools to help make our lives easier. consequently, we gravitate towards different tools such as high level programming languages, design paradigms, architecture patterns. Fundamentally, we are looking for ways to encourage formal verification. Functional programming provide an elegant framework in which to address these goals. Pure functions make your code more concise and predictable, and as a result, it also becomes easier to test than imperative or object oriented code.
3. Pure Functions.
A pure function is a function which given the same input will always return the same output, has no side-effects — any state change observable outside the called function — including other properties that are important in functional programming, such as referential transparency.
Function composition is the process of combining multiple functions in order to produce a new function or perform some computation. A concept derived from Lambda functions which is the origin for this style of programming.
local function toLowerCase(str)
return str:lower()
end
local function join(char)
local buildString = ""
return function(arrayOfStr)
local n = #arrayOfStr
for i = 1, n do
local word = arrayOfStr[i]
buildString = buildString + word
if i < n then
buildString = buildString + char
end
end
return buildString
end
end
local function map(f)
return function(arrayOfStr)
local newList = {}
for i, v in pairs(arrayOfStr) do
newList[i] = f(v)
end
return newList
end
end
local function split(seperator)
return function(str)
return string.split(str)
end
end
local function kebabCase(input)
return join("-")(map(toLowerCase)(split(" ")(input)))
end
print(kebabCase("Hello World")) -- "hello-world"
4. Immutability.
Immutability is a central concept of functional programming, without it the data flow in your program is “lossy”, meaning state history is abandoned which causes strange bugs to incur.
An immutable object is an object whose state cannot be modified after it’s created. Immutable objects can’t be changed at all. You can make a value immutable by deep freezing the object. In Luau, we have no way of freezing an object (til table.freeze) although we can achieve immutable operations with certain tools like Llama, an extensive library for working with immutable data.
Updating state encourages mutating it, as such shown below:
local list = { 1, 2, 3 }
local anotherList = { 4, 5, 6 }
for _, v in pairs(anotherList) do
table.insert(list, v)
end
list -- { 1, 2, 3 4, 5 , 6 }
It is possible to achieve similar result using the aforementioned Llama module, as it abstracts the logic. Under the hood, it is an immutable operation which constructs a new table, and appends elements from both the lists to be merged. However, it doesn’t mutate the old lists, thus preserving state history.
local Llama = require(path.to.submodule)
local list = { 1, 2, 3 }
local anotherList = { 4, 5, 6 }
local mergedList = Llama.List.merge(list, anotherList ) -- { 1, 2, 3, 4, 5, 6 }
list -- { 1, 2, 3 }
Closing remarks
Functional programming like other paradigms are just tools, use them when it is appropriate! There’s a lot more to functional programming that I can cover in a single post, so I will link a few resources below:
Erik Meijer: Functional Programming - YouTube
Brian Beckman: Don't fear the Monad - YouTube
Brian Beckman: The Zen of Stateless State - The State Monad - YouTube
Functors, Applicatives, And Monads In Pictures - adit.io