How can I re-assign a variable in a function in Haskell?
Asked Answered
B

6

13

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?

Bore answered 20/4, 2017 at 16:37 Comment(6)
Do you want to define a variable or do you want to re-assign an existing variable? I'm asking because your example looks like you're trying to change the value of the global elephant variable.Haig
im trying to re-assign an existing variable.Bore
In either case, I think you are hitting up S.O. too soon and need to learn more of the syntax and language primitives. I suggest you learn more Haskell by reading. For example, the wikibooks and "The Craft of Functional Programming" are good places to start.Sherrylsherurd
Almost all of the time in Haskell, it is better to create a new value than to modify an existing value. In addition to the already mentioned resources, I'd suggest "Learn You A Haskell For Great Good".Rob
You can't. Once a variable has a value, it has that value forever. Haskell is not like C.Menken
@Fl4mer, If you're asking this question, you need a good Haskell resource for beginners. I recommend learn you a haskell.Matrilocal
S
114

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
Soper answered 20/4, 2017 at 17:52 Comment(13)
While these are good things to know and use in general, I feel like this might not be the most appropriate time to talk about the StateT, monad transformers, type classes and lenses... If someone is unaware of this aspect of Haskell (not having assignment in the way a language like C does) and haven't previously come across this difference, I suspect they don't yet have the background for this and it might even scare them off (I could be wrong, of course, but I have a pretty strong suspicion...).Vardhamana
@DavidYoung I don't expect my answer will provide any immediate utility to the asker. Others have already done that. This answer is here because I've always believed it's valuable to catch occasional glimpses of things you can't understand yet, to look up from the path at your feet, and to gaze at the shape of the mountain through the clouds.Soper
This answer is utterly silly and hardly adresses the question. +1.Lissotrichous
Ah, all of this reminds me of my old question. Maybe you can incorporate some imperative syntax into your answer :D.Clarey
"to look up from the path at your feet, and to gaze at the shape of the mountain through the clouds" --- well. Climb up the mountain and you realize what looked like arrows and lenses is in fact the very same old kind of path you walked below.. just less lush and with thinner air =)Vishinsky
minus 1 this feels like bullying a beginner to me. +1 for the elephants.Kiger
Although I find this answer very interesting, I'd appreciate if the "don't do this" part was a bit clearer, otherwise this sounds a lot like bullying.Whydah
Doesn't seem like bullying to me, but it definitely seems like Chris wrote this answer for his own pleasure rather than to be helpful to anyone.Longshoreman
The comments seem to imply this is a bad/silly/non- answer, but this is the only answer which answers the question(unless you interpret it literally as "why does this syntax not parse"). The other answers only say "you can't do it - here is how to rewrite an imperative, mutable program into a functional, immutable one". This answer tells one how to define something to model mutable, global variables. (but perhaps this answer would benefit from less liberal use of esoteric symbols)Kilmarnock
@Kilmarnock May God have mercy on the souls who technically answer the question when an actual answer is the worst possible thing for anyone reading.Dulciedulcify
This answer reminded me of this post on fizzbuzz: joelgrus.com/2016/05/23/fizz-buzz-in-tensorflowSalzman
@Chris, ghc 8.0.2 outputs Parse error: naked expressions at top level - makeLenses ''Congo. How could one cope with it? Thanks.Forwarding
@BulatM. It sounds like the Template Haskell extension isn't enabled. Did you include the first line {-# LANGUAGE TemplateHaskell #-}?Soper
H
44

im trying to re-assign an existing variable

You can't do that in Haskell. You can do something close by using IORefs, 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.

Haig answered 20/4, 2017 at 16:46 Comment(1)
At this point I doubt there is any program logic to redesign. The OP just wants to know how to do stuff in Haskell that they do in other langs.Motta
M
27

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.

Motta answered 21/4, 2017 at 5:5 Comment(0)
T
17

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.

Tantalous answered 21/4, 2017 at 11:47 Comment(0)
P
3

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
Presumption answered 22/4, 2017 at 13:49 Comment(2)
You can indeed do this, but it's important to emphasize that the let elephant = 1 and let elephant = 2 do not re-assign the variable elephant. Instead, they just introduce a new variable, which happens to also be called elephant. The print elephant then picks the nearest previous occurence (more precisely speaking: the one in the narrowest shared scope), but outside of this scope the old elephant always keeps the same value. I recommend this should not be done in actual Haskell code (in fact ghc -Wall warns about it).Lissotrichous
@Lissotrichous You're right I added a bit to illustrate the difference. Also, I know it's not recommended but the question goes against everything the language stands for. It's hard to answer the question with something that can be recommended.Presumption
U
0

Using the same example as @Chi, you can use the State monad to simulate an imperative loop with recursion:

C code:

// sum 0..100
i = s = 0;
while (i <= 100) {
   s = s+i;
   i++;
}
return s;

Haskell code:

import Control.Monad.State

final_s :: Int
final_s = evalState sum_loop (0, 0)  -- evaluate loop with initial state (0, 0)

sum_loop :: State (Int, Int) Int
sum_loop = do
  (i, s) <- get           -- retrieve current state
  if i <= 100             -- check loop condition
    then do               -- if condition is true:
      let new_s = s + i
      let new_i = i + 1   
      put (new_i, new_s)  -- update state with new tuple
      sum_loop            -- recursively call loop with new state, simulate iteration with recursion
    else
      return s            -- if condition is false, return s as final result

main = print final_s

As you can see this is quite similar to the C code, we just have 3 more lines:

  • (i, s) <- get to get the current state.
  • put (new_i, new_s) to update the current state with the new state
  • sum_loop to recursively call loop with new state, simulating iteration with recursion

You can add debug only printing with put $ traceShowId (new_i, new_s) instead of put (new_i, new_s), but you should only use this for debugging because it cheats the type system.

So a few things more things have to handled "manually" but it is possible to write reasonably readable imperative code in Haskell.

Ultrasonic answered 11/3, 2023 at 14:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.