Immutability
Once a variable or object is set, its value, state cannot be changed.
Advantages of Immutability
Predictability, Concurrency, and Stability
Immutable objects do not change their state, making their outcomes predictable. Even when code is executed simultaneously across multiple threads or processes, immutable objects can be safely and stably shared without concerns about state changes (side-effects).
Functional Programming
It promotes effective functional programming by always returning the same output for the same input and not altering external states, combined with the concept of pure functions.
What is Functional Programming?
It's a programming paradigm that defines the computational process as much as possible through functions and minimizes the side effects of state and data changes in applications. The main characteristics and principles of functional programming include pure functions, immutability, first-class functions, closures, higher-order functions, recursion (preferring recursion over control statements like for, while), and referential transparency (uniform output for the same input, no side-effects). However, there are situations in functional programming where data needs to be changed. In such cases, patterns like monads are used.
What is a First-Class Function?
A first-class function refers to the concept where functions are treated as first-class citizens. This means that functions can be used and behave just like other values (such as numbers, strings, objects). By employing this concept, advanced programming patterns like closures and higher-order functions can be utilized. Consequently, functions can be assigned to variables, passed as arguments to other functions, returned as results from other functions, dynamically assigned properties, and stored in data structures like arrays or other objects.
# Assigning to a variable
def greet():
return "Hello!"
hello = greet
print(hello()) # Output: Hello!
# Passing a function as an argument to another function
def greet(name):
return f"Hello, {name}!"
def welcome_message(func, name):
return func(name)
print(welcome_message(greet, "Alice")) # 출력: Hello, Alice
# Returning a function from another function
def outer_function(greeting):
def inner_function(name):
return f"{greeting}, {name}!"
return inner_function
greet_hello = outer_function("Hello")
print(greet_hello("Bob")) # 출력: Hello, Bob!
# Dynamically assigning a property to a function
def greet():
return "Hello!"
greet.language = "English"
print(greet.language) # 출력: English
# Storing functions in an array or other data structure
def greet_english():
return "Hello!"
def greet_spanish():
return "¡Hola!"
greetings = [greet_english, greet_spanish]
for greet_func in greetings:
print(greet_func()) # Output: Hello! 그리고 ¡Hola!
What is a Closure?
def outer_function(x):
def inner_function(y):
return x + y
return inner_function
adder_5 = outer_function(5)
print(adder_5(3)) # 출력: 8
As you can see, there's an inner function defined inside an outer function, and the outer function returns the inner function.
When we do adder_5 = outer_function(5), we are passing 5 as an argument to the outer function. Even after the outer function has been called and it has finished executing, the state and data of its scope are still accessible to the inner function. This phenomenon is called a closure.
What is a Higher-Order Function?
A higher-order function is a function that takes other functions as arguments or returns them. Higher-order functions treat functions as data.
def apply_function(func, value): # Higher-order function - taking a function as an argument
return func(value)
def double(x):
return x * 2
print(apply_function(double, 5)) # Passing a function as an argument and applying func to the given value. Output: 10
def make_multiplier(factor): # Higher-order function - returning a function
def multiplier(x):
return x * factor
return multiplier
In the case of higher-order functions that return a function, sometimes they are closures and sometimes they are not.
Imperative Programming
As a concept opposed to functional programming, imperative programming allows for changes in the application's state and the program's output is determined by procedural flow. Logic is implemented using control structures such as loops and conditional statements. Functional programming and imperative programming are not purely separated; in real-world development environments, they are used together. However, each programming style offers a different perspective on program structure and problem-solving.
Immutability and Functional Programming in Python
In Python, immutable objects like tuples and strings are provided.
#tuple
tuple = (1, 2, 3, 4)
print(tuple)
# (1, 2, 3, 4)
tuple[1] = 222
"""
Traceback (most recent call last):
File "main.py", line 5, in <module>
immutable_tuple[1] = 222
TypeError: 'tuple' object does not support item assignment
"""
#string
immutable_string = "Hello"
immutable_string[0] = "Y"
"""
Traceback (most recent call last):
File "main.py", line 2, in <module>
immutable_string[0] = "Y"
TypeError: 'str' object does not support item assignment
"""
immutable_string = "Hello"
immutable_string = "Yello"
print(immutable_string)
One might find it puzzling when we say strings are immutable. However, the original string object remains unchanged. When you modify a string, a new string is created by copying the original (Copy on Write).
Python supports the concepts of first-class functions, higher-order functions, and closures. The previous examples were all written in Python.
Python also supports the concept of decorators.
Functional Programming in Python - Decorators
A decorator is a design pattern used to decorate or modify another function. It's primarily used to add additional functionality to a function or adjust its capabilities without modifying its original behavior.
def my_decorator(func):
def wrapper():
print("Something happening before the function is called.")
func()
print("Something happening after the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
// result
Something happening before the function is called.
Hello!
Something happening after the function is called.
Immutability and Functional Programming in Go
In Go, unlike Python, there aren't explicit immutable data structures. However, immutability can be maintained through the concept of receivers.
package main
import "fmt"
type Person struct {
Name string
Age int
}
func UpdateAge(p Person) {
p.Age = 30
}
func UpdateAgeWithPointer(p *Person) {
p.Age = 30
}
func main() {
bob := Person{Name: "Bob", Age: 25}
UpdateAge(bob)
fmt.Println(bob.Age) // Output: 25
UpdateAgeWithPointer(&bob)
fmt.Println(bob.Age) // Output: 30
}
Functions using value receivers, like UpdateAge, take a copy of the person and modify the age, so the age of the original bob object doesn't change.
Functions using pointer receivers, like UpdateAgeWithPointer, take a pointer to the struct and modify the age, so the age of the original bob object gets changed.
Higher-Order Functions in Go
package main
import "fmt"
//Higher-order function that takes another function as an argument
func applyFunction(f func(int) int, value int) int {
return f(value)
}
func double(x int) int {
return x * 2
}
func main() {
fmt.Println(applyFunction(double, 5)) // Output: 10
}
//Higher-order function that returns another function (but is not a closure
func simpleFunction() func() string {
return func() string {
return "Hello, World!"
}
}
func main() {
helloFunc := simpleFunction()
fmt.Println(helloFunc()) // Output: Hello, World!
}
Closures in Go
package main
import "fmt"
func outerFunc(x int) func(int) int {
return func(y int) int {
return x + y
}
}
func main() {
closure := outerFunc(10)
fmt.Println(closure(5)) // Output: 15
}
First-Class Functions in Go
package main
import "fmt"
func greet() string {
return "Hello, World!"
}
func main() {
hello := greet // Assigning function to a variable
fmt.Println(hello()) // Output: Hello, World!
}
댓글