Introducing the Haskell language
- Tram Ho
Functional Programming
First, let’s consider two concepts:
• Imperative Programming: Programming commands, we use commands to change the state of the program. We will guide the program to step by step from beginning to end, or repeat certain commands, …
• Declarative Programming: Programming declarations, we will tell the program what we want, not how to do it. We do not specify the steps as in order programming.
Functional Programming is a branch in Declarative Programiming, in which, we will take the function as a basic unit (similar in object-oriented programming, the object will be the basic unit). In general, we have the following characteristics:
1. Purity
A function called pure
if it takes parameters is a value and always returns a value without changing any external state. And with the same input, the function will return a corresponding value.
For example, consider a function in Swift
as follows:
1 2 3 | func add (_ a: Int, _ b: Int) -> Int { return a + b } |
This function is pure
because it doesn’t change anything outside. However, the following function is not pure
because of the same input that returns different values:
1 2 3 | func radd (_ a: Int, _ b: Int) -> Int { return Int.random (in: 0 ... 10) * (a + b) } |
But like that, printing a line of text to the screen also causes a change of state, so how do I use only pure functions? In Functional Programming, we must also have IO
operations, which are impure
operations, they will be encapsulated into one place to facilitate management. The function of Functional Programming is to minimize the side effects by accessing them, not completely removing them.
2. Immutability
There will be no change in a numerical variable
in Functional Programming, a value stored in a variable
will forever receive that value, generally it is a const variable
. You have learned that Swift
will definitely be used to a lot of let
keywords, there are probably many reasons for this, we see that it minimizes errors when using const variable
(and one of the main reasons is that swiftc
will roar every time a variable just assigns once, we use var
)
3. Recursion
In Functional Programming, we do not have keywords like for, while, repeat, forEach, ...
To implement loops, we will use recursion. For example:
1 2 3 4 5 6 | int max (int const * const arr, const int n) { if (n == 1) return * arr; int const carry = max (arr + 1, n - 1); if (carry> * arr) return carry; return * arr; } |
A function that finds the largest value in an integer array by recursion must be familiar to many people. Here int const* const
indicates that arr
must point to a fixed address and the values it points to must be fixed to preserve immutability
(the compiler will almost always change something) .
Haskell
Haskell
is a pure functional programming language (Pure Functional Programming), is a compiled language, and is a static language, but don’t worry too much, the compiler will help us a lot in self-debugging. . Haskell
is lazy (Lazy evaluation), unless forced to do it, Haskell
will never execute calculations. A simple example has two repeat
and take
functions as follows:
1 2 3 4 5 6 7 | repeat :: a -> [a] repeat x = x: repeat x take :: Int -> [a] -> [a] take n _ | n <= 0 = [] take _ [] = [] take n (x: xs) = x: take (n-1) xs |
If it is Python
, we will write the following:
1 2 3 4 5 | def repeat (x): return [x] + repeat (x) def take (n, seq): if n <= 0 or seq == []: return [] return [x] + take (n-1) seq [1:] |
However, in Python
, when we call take(3, repeat(7))
, the program will calculate repeat(7)
before and after take
later. However, when calculating repeat(7)
, we will be immediately subjected to a maximum recursion depth exceeded
because repeat(7)
is an infinite list
of 7
(We can edit the repeat
function to make it a generator
but here not the main purpose of the article). Now look at Haskell
, if anyone doesn’t know, f x1 x2 ...
is the implementation of a function f
with the parameters x1, x2, ...
and x:xs
can be interpreted as x
as the first element of a list
and xs
are interpreted as “high probability is the rest” (not the “rest”, only “high probability is the rest”). Now look at Haskell
deploying the take 3 (repeat 7)
:
1 2 3 4 5 6 7 8 9 | take 3 (repeat 7) = take 3 (7: repeat 7) = 7: take 2 (repeat 7) = 7: take 2 (7: repeat 7) = 7: 7: take 1 (repeat 7) = 7: 7: take 1 (7: repeat 7) = 7: 7: 7: take 0 (repeat 7) = 7: 7: 7: [] = [7, 7, 7] |
With sample (x:xs)
in the definition, it will convert repeat 7
to 7 : repeat 7
whenever called. Everything Haskell
pure is lazy, but there is still a function that allows us to pre-calculate the parameter value before being executed (and of course it cannot be written entirely in Haskell
). Haskell
‘s laziness also causes some troubles:
1 2 3 4 5 | lazySum (Num a) => [a] -> a lazySum = go 0 where go curr [] = curr go curr (x: xs) = go (curr + x) xs |
This function takes a list and returns the sum of the elements of that list, with the lazyness of the language, it will do the following:
1 2 3 4 5 6 7 8 9 10 11 | lazySum [1, 2, 3, 4] go 0 [1, 2, 3, 4] go (1 + 0) [2, 3, 4] go (2 + (1 + 0)) [3, 4] go (3 + (2 + (1 + 0))) [4] go (4 + (3 + (2 + (1 + 0))))]] 4 + (3 + (2 + (1 + 0)))) 4 + (3 + (2 + 1)) 4 + (3 + 3) 4 + 6 ten |
Having too many expressions not yet evaluated, Haskell
now creates too much thunk
. To limit this, as introduced above, there is a function that allows to evaluate a certain value first, Haskell
gives us seq
:
1 2 | seq ab = b a `seq` b = b |
Where a
will be evaluated, or simpler we just need to put !
before the variable we need to evaluate before the function runs with the -XBangPatterns
command flag. So we will rewrite the above function as follows:
1 2 3 4 5 | betterSum (Num a) => [a] -> a betterSum = go 0 where go curr [] = curr go curr (x: xs) = curr `seq` go (x + curr) xs |
or:
1 2 3 4 5 | betterSum (Num a) => [a] -> a betterSum = go 0 where go curr [] = curr go! curr (x: xs) = go (x + curr) xs |
And let’s look at how this function works much better than before:
1 2 3 4 5 6 7 8 | betterSum [1, 2, 3, 4] go 0 [1, 2, 3, 4] go (1 + 0) [2, 3, 4] go (2 + 1) [3, 4] go (3 + 3) [4] go (4 + 6) [] 4 + 6 ten |
With &&
, it is also a function:
1 2 3 | (&&) :: Bool -> Bool -> Bool True && x = x False && _ = False |
We perform the following two operations:
1 2 | False && (10 ^ 1000000> 5) False &&! (10 ^ 1000000> 5) |
The following calculation will be slower because we force Haskell
to calculate the value behind while not being needed.
What Haskell
compiles and how long it takes to run is hard to predict, remembering the garbage collector so Haskell
often not suitable for embedded systems, where memory management and security are needed. absolute latency. Also the source code of a binary binary file from Haskell
is very large, so it is not suitable for embedded systems with limited memory. As well as writing an application with C++
frameworks like Qt
, Haskell
in application programming is also very painful. Haskell
is best used when creating a compiler
, concurenccy
or parsing …
The syntax of the language is more mathematical than informatics, for example, to represent a set of even numbers in paragraphs 1
to 10
, mathematically, we will write left overline {1, 10} | 2 | x right } {x ∈ 1, 10 | 2 | x} or left {x | x inline {1, 10}; wedge 2 | x right } {x | x ∈ 1, 10 ∧ 2 | x}. With the second way of writing, we will implement it with Haskell
with a simple switch : ∈ to <-
:
1 | [x | x <- [1..10], x `mod` 2 == 0] |
The results will return [2, 4, 6, 8, 10]
. To list the set of 3 integers without negative numbers with a sum equal to 6, we will have:
1 | [(a, b, c) | a <- [0..6], b <- [0..6], c <- [0..6], a + b + c == 6] |
I learned Haskell
just because of this, regression, combinatorial, … formula models can be implemented by Haskell. For example, the problem of counting to see how much the n
non-negative integers whose sum is m
. Of course according to Euler candy division result will be Binom {n – 1} {m + n – 1} (m + n – 1 n – 1), but let’s pretend naive as to what it was working with Bayes’ theorem in Machine Learning, we will list all into a list and count the number of elements of that list:
1 2 3 | tupleWithSum :: Integer -> Integer -> [[Integer]] tupleWithSum 1 m = [[m]] tupleWithSum nm = [x: xs | x <- [0..m], xs <- tupleWithSum (n - 1) (m - x)] |
We perform length $ tupleWithSum 3 6
, the results of 28
primary by Binom {3-1} {8 + 3-1} (8 + 3-1 3-1). Look at that function and design it with the mathematical model, it will be written S_ {n + 1, m} = left {(x, A); x overline {0, m}, A in S_ {n-1, m – x} right } S n + 1, m = {(x, A) | x ∈ 0, m, A ∈ S n – 1, m – x } .
Thanks to Haskell
, I learned how to deploy something like quy hoạch động
through đệ quy
:
1 2 3 4 | fib = go 0 1 where go a _ 0 = a go abn = go b (a + b) (n - 1) |
Overall, Haskell
made me more interested than I thought, even though it was difficult, but it was worth studying for programming thinking.
How to choose a programming language is reasonable?
Source : viblo