Hanabi Factory

Navi's Personal Blog


Notes about Functional Programing with Julia

Notes about Functional Programing with Julia

I am writing here some general ideas that were taken from some sources like boot.dev about functional programming, many of these sources were written in Python and I just rewrote Julia in most of the cases. Because Julia is a program more suitable for FP I considered a good exercise in the long run to translate the concepts that I am learning about this paradigm.

Index

\toc

What is Functional Programming

  • compose functions instead of mutating states,
  • What you want to happen rather than how you want to happen

Inmutability

Once the Value is created it can’t be changed, this can be easier to debug

Declarative

Functional aims to be declarative rather than imperative

Math Style

imperative style

function get_average(nums)
    total = 0
    for num in nums
        total += num
    end
    return total / length(nums)
end

functional style

function get_average(nums)
    return sum(nums) / length(nums)
end

In general to make a bit more functional style, we should avoid loops and mutate any variable

Classes encourage you to think about the world as a hierarchical collection of objects. Objects bundle behavior, data, and state together in a way that draws boundaries between instances of things, like chess pieces on a board.

Functions encourage you to think about the world as a series of data transformations. Functions take data as input and return a transformed output. For example, a function might take the entire state of a chess board and a move as inputs, and return the new state of the board as output.

OOP is not quite the opposite with FP, but the 4 pillars of the first one (abstraction, encapsulation, inheritance and polymorphism) inheritance is the one that can produce changes in classes, so break the rule of inmutability in FP

Functions are First Class

We can treat functions as values

function add(x,y)
    return x+ y
end

addition = add

println(addition(2,7)

# print 9

Anonymous Functions

Basically functions that doesn’t have name, similar like python use lambda functions

function filter_var(df, value)
    return filter!(row -> row.colum != value , df)
end

in the last case row -> row.colum != value is an anonymous function

Higher Order Functions

In the case that the programming language threat functions like any other variable, so Functions are first class then we can pass functions as an arguments to other functions.

function square(x)
    return x * x
end

function my_map(func, arg_list)
    result = []
    for i in arg_list
        push!(result, func(i))
    end
    return result
end

squares = my_map(square, [1, 2, 3, 4, 5])
println(squares)
# [1, 4, 9, 16, 25]

In the last case my_map() is a higher order function

Map, Filter and Reduce

Map, filter and reduce are three typical examples of Higher order functions that are quite useful, for a map function you need an iterable (An object capable of returning its members one at a time.) and a function, and apply the function to all the elements of this iterable

function say_hello(name)
    return "Hello " * name
end

list_names = ["Chris", "Hector", "Benito"]

map(say_hello, list_names)
# ["Hello Chris, "Hello Hector", "Hello Benito"]

Filter was already shown in an example before, but basically takes an iterable, a function and return also an iterable that is a subset of the original.

Finally the Reduce function take same arguments but now it reduce everything to a single value, like the following example

function add(sum_so_far, x)
    prinln("sum_so_far: $sum_so_far, x: $x")
    return sum_so_far + x
end

numbers = [1, 2, 3, 4]
sum = reduce(add, numbers)

# sum_so_far: 1, x: 2
# sum_so_far: 3, x: 3
# sum_so_far: 6, x: 4
# 10

println(sum)

# 10

This higher order functions allow us to write functions without using loops in some cases avoiding stateful iterations and mutation of variables.

Pure Functions

Pure functions has to accomplish two properties:

  • They always return the same value given the same arguments.
  • Running them causes no side effects

pure

function findMax(nums)
    max_val = -Inf
    for num in nums
        if max_val < num
            max_val = num
        end
    end
    return max_val
end

Let’s compare with this other case

# instead of returning a value
# this function modifies a global variable
global_max = -Inf

function findMax(nums)
    global global_max
    for num in nums
        if global_max < num
            global_max = num
        end
    end
end

In the first case we keep a function which clearly define an input and return and output while in the second case we produce a global variable that change the state of this (breaking the rule of inmutability) and does not return anything but our global variable has changed. In summary, pure functions:

  • Return the same result if given the same input, so they are deterministic (which no randomness is involved in the development of future states of the system.). Also there is the term referentially transparent
  • Do not change the external state of the program. For example, they do not change any variables outside of their scope.
  • Do not perform any I/O operation like printing, accessing to data via HTTP or reading files.

Reference and Value

There are functions that allow you to pass by references, this are mutable, you can see this when appending values in a list. In this case the function has access to the original value. For other side a function that receive variables as values are receiving copy of the original and do not attemt to change the original (inmutability), you can do in Julia using deepcopy(var) to create copies

Pass by Reference Impurity

To avoid side effects we can create copies of the variables inside of a function without changing any variables that is out of the scope (this includes the input of the function)

function remove_format(default_formats, old_format)
    new_formats = deepcopy(default_formats)
    new_formats[old_format] = false
    return new_formats
end

With this we avoid mutating any input or global variable making it easier to debug and test.

Input and Output

While I/O operations are part of impure functions, these are necessaries (or our program is completely useless) so It tries to use only when is neccesary.

NO-OP

Functions that does nothing, or better said doesn’t return anything, probably are impure functions

function square(x)
    x * x
end

That function doesn’t do anything, but also there are functions that perform some side effect:

y = 5
function add_to_y(x)
    global y
    y += x
end

add_to_y(3)
# y = 8

Even the print() function technically has an impure side effect

Memoization

This is storing a copy of a result a computation so we don’t have it to compute it again in the future, it holds a trade-off between memory and speed. This only can be achieved with pure functions.

const fibmem = Dict{Int,Int}()
function fib(n)
    get!(fibmem, n) do
        n < 3 ? 1 : fib(n-1) + fib(n-2)
    end
end

Recursion

Function that define itself, for example the classic factorial. This kind of functions are quite useful for unknown tree structure

function factorial_rec(x)
    if x == 0 
        return 1
    else
        return x * factorial_rec(x - 1)
    end
end

julia> factorial_rec(0)
1

julia> factorial_rec(3)
6

A recursive function should have some dangerous edge case that deserve attention:

  1. Requires base case to avoid infinite loops.
  2. Each function call requires a bit of memory, so in long trees structures can cause a stack overflow and will crash your program
  3. In some languages recursion is slow, like python where is even slower than loops. Use of Tail call Optimizations can deal with that

Function Transformations

Specific type of Higher order functions that receive functions as input and return functions as output, special for some cases of code reusability

function multiply(x, y)
    return x * y
end

function add(x, y)
    return x + y
end

# self_math is a higher order function
# input: a function that takes two arguments and returns a value
# output: a new function that takes one argument and returns a value
function self_math(math_func)
    function inner_func(x)
        return math_func(x, x)
    end
    return inner_func
end

square_func = self_math(multiply)
double_func = self_math(add)

println(square_func(5))
# prints 25

println(double_func(5))
# prints 10

Closures

A closure is a function that references variables from outside its own function body. The function definition and its environment are bundled together into a single entity so a closure can change the value outside its body

closure

julia> function make_adder(amount)
           function add(x)
               return x + amount
           end
       end;

julia> add_one = make_adder(1);

julia> add_two = make_adder(2);

julia> 10 |> add_one
11

julia> 10 |> add_two
12

In the case of Julia, generate global variables can cause Type Instability and there are some discussions about avoiding closures when performance is required, However that doesn’t mean that using closures should be avoided completely, a lot of discussions are here also interesting content here

Naturally if a function can change a a non local variable then is not a pure function, so many cases closures are not pure functions because they can mutate outside of their scope and have side effects.

Notice that also there are concept of Decorators in some languages like Python, that are just syntactic sugar for higher order functions

Currying

Function currying is a specific kind of function transformation where we translate a single function that accepts multiple arguments into multiple functions that each accept a single argument.

currying

This is a normal function without currying

function sum(a,b)
    return a+b
end

With currying

function sum(a)
    function inner_sum(b)
        return a + b
    end
    return inner_sum
end

With this option now we can return a function as a value (inner_sum) and change it’s signature to make it conform to specific parameter

Wrapping up

These are just basic ideas about functional programming, there are more concepts to deal with, but at least here is an starting point for people like me who is not a cs person…

Articles from blogs I follow around the net

Alpha Capture and Acquired

People are never short of a trade idea. There is a whole industry of researchers, salespeople and amateurs coming up with trading ideas and making big calls on what stock will go up, what country will cut interest rates and what the price of gold will do …

via Dean Markwick September 19, 2024

Is Basketball a Random Walk?

About two years ago, I attended a seminar given by Dr. Sid Redner of the Santa Fe Institute titled, “Is Basketball Scoring a Random Walk?” I was certainly skeptical that such an exciting game shared similarities with coin flipping, but, nevertheless, Dr. …

via HasItHalted August 17, 2024

Consciousness and Materialism

Hume’s Parallel David Hume has often been quoted for his “Is” vs. “Ought” distinction. The argument is that fact and morality are two different domains, and from no accumulation of statements of fact alone can we ever jump to a statement of morality. We can …

via Luke Smith's Webpage on Luke's Webpage January 16, 2023

Generated by openring