Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Define variable in list comprehension format function

Tags:

list

haskell

I have this pseudocode that calculates the factorial of a number given by the user.

Process without_title
     Define i, tmp, x As Integers;
     tmp<-1;
     Write "Enter a number to calculate its factorial";
     Read x;
     For i <- 1 Until x Do
         tmp<-tmp * i;
     EndTo
     Write tmp;
EndProcess

Now I would like to implement the same code in Haskell using list comprehensions

import Data.IORef

factorial::Int -> Int
factorial x = [tmp|i<-[1..x], modifyIORef tmp (*i)]

Throws error saying tmp variable not defined

How do I define the tmp variable, so that I can use it?

like image 503
Alejandro Caro Avatar asked May 26 '26 15:05

Alejandro Caro


2 Answers

To implement a factorial function using a mutable IORef like this you will need to work in the IO monad and use a newIORef operation to create your IORef and then use forM_, traverse_, or sequence_ or a similar function to sequence a set of IO operations on that IORef. It might look something like:

import Control.Monad
import Data.IORef

factorial :: Int -> IO Int
factorial x = do
  tmp <- newIORef 1
  forM_ [1..x] $ \i -> modifyIORef' tmp (*i)
  readIORef tmp

or, if you prefer to use a list comprehension so it looks a little more like your attempt above, something like:

import Data.IORef

factorialIO :: Int -> IO Int
factorialIO x = do
  tmp <- newIORef 1
  sequence_ [modifyIORef' tmp (*i) | i <- [1..x]]
  readIORef tmp

(Using the strict modifyIORef' provides better performance than the lazy modifyIORef.)

This can then be called like so:

main :: IO ()
main = do
  result <- factorial 10
  print $ result

This will generally be slower than a more direct, "pure" solution like:

factorial :: Int -> Int
factorial x = product [1..x]

My benchmarks suggest that the IORef-based version is about two times slower than the pure product-based version.

like image 122
K. A. Buhr Avatar answered May 28 '26 06:05

K. A. Buhr


To complement the existing answers: a problem is that if you start involving IO, you cannot have an IO-free type signature like factorial :: Int -> Int for your function.

Another (easier to deal with) problem is that if you return a list comprehension expression directly, the result type of your function has to be some sort of list. It cannot be a scalar value such as Int.

A possible compromise is to use the Haskell state monad, with the tmp product as the state. You can generate the list of required multiplication operations with a suitable list comprehension expression, and then combine them using the sequence_ library function.

Like this:

import  Control.Monad.State (state, runState, sequence)

factorial :: Int -> Int
factorial x = finalTmp  where
    tmp0           = 1  -- our initial state
    actions        = [ state (\tmp -> ((), i*tmp)) | i <- [1..x] ]
    (rs, finalTmp) = runState  (sequence_ actions)  tmp0

using tmp as the state as per your notations, even though using st would be more idiomatic.

like image 26
jpmarinier Avatar answered May 28 '26 05:05

jpmarinier