WRITING: Started writing the functional article

This commit is contained in:
Hayden Hargreaves 2025-04-28 23:40:35 -07:00
parent 3212a5ccfb
commit 867585c7cc
2 changed files with 257 additions and 0 deletions

View File

@ -0,0 +1,257 @@
Date: 2025/04/28
Desc: I have decided to begin the journey of learning functional programming. Here is my experience.
# What is a Functional Programming Language: Featuring Elixir.md
<img src="/journal/elixir-logo.png" alt="Jet Brains Logo" style="background-color: white; border-radius: 15px; padding: 10px; margin-inline: 10px; margin-block: 2.5%;" width="300">
<br>
###### Author: Hayden Hargreaves
###### Published: 05/??/2025
## Background
Many programmers tend to avoid **functional programming** due to its perceived complexity, myself included.
But is functional programming really that much more complex? My goal is to break my fear of functional
programming, [monads](https://en.wikipedia.org/wiki/Monad_(functional_programming)), [functors](https://en.wikipedia.org/wiki/Functor), and all those *scary* terms frequently tossed around in the functional
space.
How am I going to do this? Well, I am definitely not going to start with [Haskell](https://www.haskell.org). For those who have
never seen or heard of Haskell, this decision might be hard to understand. I will not explain myself too
much. But I will provide a code snippet from the Haskell docs and let you decide if it a worthy language
for someone who has never written a line of functional code in their life.
```haskell
primes = filterPrime [2..] where
filterPrime (p:xs) =
p : filterPrime [x | x <- xs, x `mod` p /= 0]
```
Personally, the Haskell language is far too esoteric for my liking. Eventually, I would love to be able
to, at the very least, **read** Haskell code, but not yet. So this put me a spot to select from a large
selection of languages. I did not want to use a language like **Python** or **JavaScript** which can be
written to *seem* functional. No, I wanted to write a **real** functional language. A common list of as
follows:
- [Haskell](https://www.haskell.org)
- [Erlang](https://www.erlang.org)
- **[Elixir](https://elixir-lang.org)**
- [Scala](https://www.scala-lang.org)
- [Clojure](https://clojure.org)
- [OCaml](https://ocaml.org)
- [Common Lisp](https://lisp-lang.org)
To avoid offending all of the "functional bros" I will not explain my thought process much. However, I
will mention that I tried to learn **OCaml** a years ago and could not enjoy it. Maybe that was because I
was not as advanced as I am today, or maybe I did not have the proper mindset. Regardless, that ruled
out OCaml. I have no experience in any of the other languages, so I did what most would do. I let Reddit
decide! The [Elixir](https://www.reddit.com/r/elixir/) Reddit page was very informative as well as providing me with a link to the
[2024 Stack Overflow Survey](https://survey.stackoverflow.co/2024/technology#top-paying-technologies) which described Erlang and Elixir as the top two paying languages.
With that out of the way, lets talk more about what functional programming is.
## What is Functional Programming
At the most basic level, functional programming is defined as a ["programming paradigm where programs are
constructed by applying and composing functions"](https://en.wikipedia.org/wiki/Functional_programming). In laymen terms, a majority of the code written in these
languages is just functions. Everything can be expressed as some composition of functions. For this reason,
a strong understanding of mathematics can be hugely beneficial to functional programmers.
>
> "Functional programming evolved from lambda calculus"
>
#### Pure Functions
Another key difference between other paradigms is the strict immutability. Functional programming introduces
the term "[pure function](https://en.wikipedia.org/wiki/Pure_function)" which is any function that can be run (any amount of times) and will always produce
the same output, **and cannot be affected by (or affect) any mutable state.** The intention of writing pure
functions is to prevent [side effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science)), "any observable effect other than its primary purpose."
To simplify, the two properties of a **pure** function are:
1) The function will return an identical result for identical arguments. With ***no*** variation for any reason,
including reference arguments.
2) The function has no *side effects*, no mutation of local static variables, non-local variables, etc.)
An example of a pure function in C++ may look as follows:
```c++
void f() {
static std::atomic<unsigned int> x = 0;
++x;
}
```
The function `f()` is pure because it follows the above properties.
```c++
int f_i() {
static int x = 0;
++x;
return x;
}
```
However, the function `f_i()` is impure because it returns a variation of a static variable.
Countless more examples of impure functions can be found [here](https://en.wikipedia.org/wiki/Pure_function#Impure_functions).
**NOTE:** I wanted to write these examples in Elixir, but due to its functional nature, it would be very hard.
<br>
#### Functions are First Class Citizens
Another key property of functional programming is that functions are known as [first class citizens](https://en.wikipedia.org/wiki/First-class_citizen) which
means they can be assigned to variables, passed into other functions as arguments ([higher order functions](https://en.wikipedia.org/wiki/Higher-order_function))
and returned from functions. This is not uncommon in modern programming languages so I will not provide an
in-depth explanation.
<br>
#### Recursion
The death of many modern programming languages, recursion, is one of the many strengths of functional languages.
[Recursion](https://en.wikipedia.org/wiki/Recursion_(computer_science)) occurs when a functions calls itself. This is the most common way to implement [iteration](https://en.wikipedia.org/wiki/Iteration) in
functional programming. For example a simple loop in any common procedural language:
```c++
std::vector<int> numbers = {1, 2, 4, 8, 16};
for (size_t i = 0; i < numbers.size(); i++) {
std::cout << numbers[i] << " ";
}
```
Can be written in a similar way using recursion with Elixir:
```Elixir
def print_list([]), do: IO.puts("") # Base case: when the list is empty, this function is called
def print_list([head | tail]) do
IO.puts(head) # Print the first element in the list (head)
print_list(tail) # Call the function again with the remaining elements
end
```
Programmers coming from other languages might freak when they see so much recursion, and it is not wrong to
worry. In procedural languages, recursion requires the stack to keep a record of each function call, and when
recursion is used to extreme levels, the stack becomes very large and uses lots of memory. Not very efficient!
However, functional programming languages found a solution, [tail recursion](https://en.wikipedia.org/wiki/Tail_call). Tail recursion allows the compiler
to creation a subroutine which represents the final action (final function call) and place it onto the stack,
allowing the program to jump right to the end without having to store the entire recursive loop in stack memory.
This reduces the memory stack space from linear or O(N) to constant or O(1). A huge performance increases in
recursive programs.
<br>
#### Strict vs. Non-Strict Evaluation
Functional languages can be categorized by *strict (eager)* or *non-strict (lazy)* evaluation. The different
evaluation styles refer to how arguments are processed inside an expression under evaluation. This concept
is quite complex and this overview does not warrant the necessity for such details. However, a simple example
can explain (on a higher level) the difference. **"Under strict evaluation, the evaluation of any term containing
a failing subterm fails.** An example makes this a bit easier to understand. We will use the same example to described
both evaluation types.
```
print length([2+1, 3*2, 1/0, 5-4])
```
In a language categorized as *strict*, the expression above will fail, due to the term `1/0` failing. However, in a language
with *non-strict* evaluation, the length expression will return 4 because it does attempt to evaluate the subterms, which
result in failure.
To summarize, *lazy* evaluation does not attempt to evaluate function arguments unless their values are required for the
function call itself.
***NOTE: Elixir is a strictly evaluated language.***
<br>
#### Referential Transparency
Another very detailed differentiation from imperative programming languages. [Referential transparency](https://en.wikipedia.org/wiki/Referential_transparency) stems from linguistic
roots and language by extension. Applications in this context (computer science) states that "a language is *referentially
transparent* when an expression built from another, replaces the expression with a subexpression which represents the same value
and does not change the value of the expression." That sounds very complicated, and it is, but to be simple, expressions cannot
be modified, only replaced.
Functional programming languages do not have **assignment statements**, because a value can never be changed once it is defined.
This is a result of the language allowing variables to be swapped with their value at any time. Therefore, functional languages
are referentially transparent.
Another example will make this easier to understand. Consider the following assignment statement from [C](https://en.wikipedia.org/wiki/C_(programming_language)). This assignment changes
the value assigned to the variable `x`. If the initial value of `x` was to be `1`, the result would be `10`. But when called again, the
result would be `100`. Since replacing `x` with `10` or `100` gives the program different meaning, it is not *referentially transparent.*
```c
x = x * 10
```
However, consider the following elixir function, `f(x)`. This implementation is *transparent*, as it does not modify the value of x,
which results in the absence of **side effects**. Instead, it returns a value which can be used to replace an existing value. This
is the standard in functional languages which results in their referential nature.
```elixir
def f(x) do
x + 1
end
```
<br>
#### Data Structures
The last key difference between imperative and function languages is their representation of data structures. Functional languages
admit a [purely functional](https://en.wikipedia.org/wiki/Purely_functional_data_structure) data structure, where the biggest difference is immutability. Purely functional data structures are
strongly immutable, which allows for many advantages, such as [persistence](https://en.wikipedia.org/wiki/Persistent_data_structure), quick copy and [thread safety](https://en.wikipedia.org/wiki/Thread_safety).
Of the advantages listed, I will only go in detail about one: **persistence.** Purely functional data structures are persistent which
means that when modifications occur, the previous state will be kept unmodified. A common comparison is to the non-persistent array
which admits a **destructive update** which cannot be undone, since no previous versions are kept.
***NOTE: Elixir is not a purely functional language and as a result, does not implement a purely functional data structure.***
## Why Functional?
That was quite a long explanation, but it covered almost everything you would need to know to *begin* learning a functional language.
However, I did not explain why **I** decided to learn. Functional languages are rarely faster (or even as fast) as imperative languages
like C, so the choice was not made for performance. The most obvious choice: ***I want to.*** So many software developers spend too much
time worrying about what is "the best" or "the fastest." Maybe they should just be learning what they **want.**
Like I stated in the background, I, like many others, run at the sight of functional languages, but its about time I break that fear!
The next sections of this article will highlight my experience learning the **Elixir Programming Language,** so if you were only here for
the functional definition, this is a good stopping point.
## Why Elixir?
I have talked a lot about Elixir language but what exactly is it? Obviously its a functional programming language, but that's very vague.
"[Elixir](https://elixir-lang.org) is a dynamic, functional language for building scalable and maintainable applications." Elixir runs on the [Erlang](https://www.erlang.org) VM which is
known for creating fault tolerant, low-latency, distributed systems. The language can be installed and testing in the interactive elixir shell
`iex`, similar to pythons interactive shell. The interactive shell was a great tool for my own learning while reading through the documentation.
Elixir was first released in **2012** which makes it a newer language, but not as new as some (Odin, or Zig). With this comes a large user base
as well as a large developer ecosystem. But, what makes elixir stand out in that aspect is its compatibility with Erlang. Erlang, which appeared
in **1986** has a huge ecosystem of libraries and tools which work seamlessly in Elixir. Elixir comes packaged with a build tool, `mix` which allows
for compilation and interpretation of elixir code. Elixir *also* has a dedicated package manager, `hex`, which is comparable to `npm` in terms of
use. However, Elixir's ecosystem does not compare to the JavaScript ecosystem (what language does?).
One of the most popular uses of Elixir is with the [Phoenix Web Framework](https://www.phoenixframework.org). Phoenix, a full stack web framework that boasts its countless features
which include **LiveView**, a tool for building real-time web applications. Another popular library, **[Ecto](https://hexdocs.pm/ecto/Ecto.html)** is a SQL ORM that is built into the Phoenix
framework and allows for seamless database connections from your web application back end. (The Phoenix framework will come up more later.)
But who cares? Well, many large companies you have heard of use Elixir in their software. Some of which include WhatsApp, Discord, Heroku, Pepsico
and [more](https://elixir-lang.org/cases.html).
## The Beginning
The repo:
https://github.com/haydenhargreaves/ElixirTodo

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB