I don't know how to re-assign a variable in a function.
For example,
elephant = 0
function x = elephant = x
Why doesn't this work?
You declare a variable; Haskell doesn't allow uninitialized variables, so you are required to supply a value in the declaration; There's no mutation, so the value given in the declaration will be the only value for that variable throughout its scope.
Assigning values to variables is achieved by the = operator. The = operator has a variable identifier on the left and a value on the right (of any value type). Assigning is done from right to left, so a statement like var sum = 5 + 3; will assign 8 to the variable sum .
return is actually just a simple function in Haskell. It does not return something. It wraps a value into a monad. Looks like return is an overloaded function.
Twice instead is an example of self-composition (function iteration), which you can express through f . f . But note that there are no overloaded functions in Haskell - Every function in one scope has exactly one type and implementation (though this type may be polymorphic).
In general this doesn't work because you usually make immutable declarations, rather than specifying a sequence of operations. You can do:
elephant = 3
main = print elephant
But you can also do:
main = print elephant
elephant = 3
Because the code doesn't specify an order of execution, there is no way to interpret multiple assignments as anything other than an error.
If you want to specify a sequence of operations, use do notation:
main = do
let elephant = 0
print elephant
let elephant = 1
print elephant
let elephant = 2
print elephant
The code in a do block is executed in order, so you can effectively reassign variables the way you can in most programming languages.
Note that this code really just creates a new binding for elephant. The old value still exists:
main = do
let elephant = 1
print elephant
let printElephant = print elephant
let elephant = 2
print elephant
printElephant
Because the printElephant function I define is still using the old value of elephant, this prints:
1
2
1
Haskell is a leader in the functional programming world and functional programming is often called "programming without assignment." It's almost the entire point of functional programming to not use assignment. As soon as you've used it, you're not really doing it in a "functional" way any more. Of course there are times for it, but FP tries to minimize those times.
So, to answer your question, "Why doesn't this work?" First of all the syntax is not correct. =
does not mean assignment in Haskell. It binds a name to an expression. You cannot do that twice (in the same scope). In other words, "variables" are immutable (like in math). Second, mutation is a side-effecting action and Haskell treats those as impure actions which must be done in the IO
world.
I could show you how to actually mutate a reference in Haskell, but I don't think that's what you need at this point.
Haskell is a great imperative language, and writing programs that can re-assign state is a really fun advanced topic! This is definitely not the approach you want right now, but come back to it some day 🙂
It takes a bit of effort to define an environment that models global mutable variables. Once you get the hang of it, though, the precision of the types ends up being pretty handy.
We're going to be using the lens and mtl libraries.
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import Control.Monad.State
I'll stick with using integers as your question does, but we'll throw in a type alias to remind ourselves that they are being used as the type of the elephant
variable.
type Elephant = Integer
You wanted a program whose global mutable state has an elephant. So first let's define what it means to have an elephant. Lens
captures this notion nicely.
class HasElephant a
where
elephant :: Lens' a Elephant
Now we can define function
, which assigns a new value to elephant
.
function :: (MonadState s m, HasElephant s) => Elephant -> m ()
function x =
elephant .= x
The constraints MonadState s m
and HasElephant s
are saying our program must be able to hold mutable state of some type s
, and the type s
must have an elephant.
Let's also define a program that prints the elephant.
printElephant :: (MonadState s m, HasElephant s, MonadIO m) => m ()
printElephant =
use elephant >>= (liftIO . print)
This program does I/O (printing), so we have an additional constraint MonadIO m
that says our program type m
must be able to do I/O.
The elephant
variable is probably only one part of some larger program state. Let's define a data type here to represent the entire state (which we'll name Congo just to be cute because the Congo Basin is one place where elephants live).
data Congo = Congo
{ _congoElephant :: Elephant
}
makeLenses ''Congo
(See Control.Lens.TH for a little bit about makeLenses
does here using Template Haskell.)
We must define the way in which the Congo
has an elephant.
instance HasElephant Congo
where
elephant = congoElephant
Now we can write an example program. Our program will print the value of elephant
, then change the value of elephant
, then print it again.
main' :: StateT Congo IO ()
main' =
do
printElephant
function 2
printElephant
Then we can run this program.
main :: IO ()
main = Congo 0 & runStateT main' & void
The output is:
0
2
im trying to re-assign an existing variable
You can't do that in Haskell. You can do something close by using IORef
s, but this is very rarely the proper solution to a problem - certainly not in situations a beginner might encounter.
Instead you should re-design your program logic, so that it does not require mutable variables to function.
The most primitive way to bind a variable x
to a value v
is to write a function taking x
as argument, and pass v
to that function.
This can sometimes be used to "simulate" the effect of a mutable variable.
E.g., the imperative code
// sum 0..100
i = s = 0;
while (i <= 100) {
s = s+i;
i++;
}
return s;
becomes
final_s = f 0 0 -- the initial values
where
f i s | i <=100 = f (i+1) (s+i) // increment i, augment s
| otherwise = s // return s at the end
The above code is not pretty FP code, but at least it is close enough to imperative code to make it possible to spot the connections.
A final digression:
When one first notices this, it is usually lured to fall into the Blub paradox. One could easily think: "What!? Haskell needs all that stuff to simulate a simple assignment? If in language Blub assignment is trivial, and simulating that in Haskell requires so much effort, then clearly Blub is much better than Haskell!". And this would be a perfect case of the Blub paradox: when a Blub programmer moves to another language, they immediately perceive what can not be directly translated from Blub, and do not notice all the other features of the new language which were not present in Blub. Their mind now thinks in "Blub", and it requires a great effort to adapt to new models.
Almost as paradoxically, learning both FP and imperative programming is useful precisely because it's non trivial to learn the other paradigm when used to only one of those. If the step between them were narrow, it would not be worth the effort to learn two close approaches to the same problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With