3

There is a sense in which Haskell is a purely functional language, and certainly, idiomatic code tries to be as functional as reasonably possible. At the same time, Haskell does provide support for fairly direct translation of some imperative patterns familiar from other languages, e.g. http://learnyouahaskell.com/a-fistful-of-monads#do-notation

(I'm aware that there is a sense in which do-notation is 'really' still functional; the point here is that it allows a fairly direct translation of some imperative design patterns.)

One pattern I'm interested in, is one where a function needs to update an outer variable, i.e. a variable that exists in an outer scope shared with other code. This can be demonstrated simply in Python:

def three():
    n = 0

    def inc():
        nonlocal n
        n += 1

    inc()
    inc()
    inc()
    return n

Is it possible, perhaps with some variant of do-notation, or otherwise, to implement the above design pattern in Haskell? If so, how?

To be clear on the scope of this question:

I'm not asking what's the best way to solve the above problem in Haskell. Obviously, the answer to that would be three = 3. It's just an example.

I'm not asking whether the above design pattern is good or bad. Obviously, that would be a matter of opinion.

I'm not asking how hard one should try, when writing Haskell, to avoid using imperative design patterns. Obviously, that too would be a matter of opinion.

I'm just asking whether the above design pattern can be implemented in Haskell, and if so, how.

duplode
  • 33,731
  • 7
  • 79
  • 150
rwallace
  • 31,405
  • 40
  • 123
  • 242
  • 3
    You've got a great answer. I would recommend that, just to keep your thinking clear, you avoid saying "variable" in Haskell when you mean something that can be modified. A good general word to use instead is "reference cell". There are many types of reference cells in Haskell: for example, `IORef`, `STRef`, and (the confusingly-named) `MVar` and `TVar`. None of these are called variables in Haskell, even the ones named `TVar` and `MVar`. – Chris Smith Jan 23 '22 at 00:22

1 Answers1

13

In Haskell, all you have to do is to create the mutable variable (actually, a reference to that) in the outer scope and use it in the inner scope.

Here I used the ST s monad to illustrate the principle, but you can do the same with IO and many other kinds of references.

import Control.Monad.ST
import Data.STRef

three :: ST s Int
three = do
   n <- newSTRef 0
   let inc = modifySTRef' n (+1)
   inc
   inc
   inc
   readSTRef n

main :: IO ()
main = print (runST three)   -- output: 3

Note that, in the general case, inc could have been a large do block, possibly creating new references (new local variables) within its scope, and having inner blocks of its own. As in python, there are no limits on the nesting of scopes.

chi
  • 111,837
  • 3
  • 133
  • 218