2

I have a simple function (used for some problems of project Euler, in fact). It turns a list of digits into a decimal number.

fromDigits :: [Int] -> Integer
fromDigits [x] = toInteger x
fromDigits (x:xs) = (toInteger x) * 10 ^ length xs + fromDigits xs

I realized that the type [Int] is not ideal. fromDigits should be able to take other inputs like e.g. sequences, maybe even foldables ...

My first idea was to replace the above code with sort of a "fold with state". What is the correct (= minimal) Haskell-category for the above function?

ruben.moor
  • 1,876
  • 15
  • 27
  • Your function does not use state, but if it did, you could use `Data.Foldable`, which provides `foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b`. As an aside, the runtime of your function is very poor. – user2407038 Oct 15 '14 at 19:09

6 Answers6

7

First, folding is already about carrying some state around. Foldable is precisely what you're looking for, there is no need for State or other monads.

Second, it'd be more natural to have the base case defined on empty lists and then the case for non-empty lists. The way it is now, the function is undefined on empty lists (while it'd be perfectly valid). And notice that [x] is just a shorthand for x : [].

In the current form the function would be almost expressible using foldr. However within foldl the list or its parts aren't available, so you can't compute length xs. (Computing length xs at every step also makes the whole function unnecessarily O(n^2).) But this can be easily avoided, if you re-thing the procedure to consume the list the other way around. The new structure of the function could look like this:

fromDigits' :: [Int] -> Integer
fromDigits' = f 0
  where
    f s []     = s
    f s (x:xs) = f (s + ...) xs

After that, try using foldl to express f and finally replace it with Foldable.foldl.

Petr
  • 62,528
  • 13
  • 153
  • 317
3

You should avoid the use of length and write your function using foldl (or foldl'):

fromDigits :: [Int] -> Integer
fromDigits ds = foldl (\s d -> s*10 + (fromIntegral d)) 0 ds

From this a generalization to any Foldable should be clear.

ErikR
  • 51,541
  • 9
  • 73
  • 124
1

A better way to solve this is to build up a list of your powers of 10. This is quite simple using iterate:

powersOf :: Num a => a -> [a]
powersOf n = iterate (*n) 1

Then you just need to multiply these powers of 10 by their respective values in the list of digits. This is easily accomplished with zipWith (*), but you have to make sure it's in the right order first. This basically just means that you should re-order your digits so that they're in descending order of magnitude instead of ascending:

zipWith (*) (powersOf 10) $ reverse xs

But we want it to return an Integer, not Int, so let's through a map fromIntegral in there

zipWith (*) (powersOf 10) $ map fromIntegral $ reverse xs

And all that's left is to sum them up

fromDigits :: [Int] -> Integer
fromDigits xs = sum $ zipWith (*) (powersOf 10) $ map fromIntegral $ reverse xs

Or for the point-free fans

fromDigits = sum . zipWith (*) (powersOf 10) . map fromIntegral . reverse

Now, you can also use a fold, which is basically just a pure for loop where the function is your loop body, the initial value is, well, the initial state, and the list you provide it is the values you're looping over. In this case, your state is a sum and what power you're on. We could make our own data type to represent this, or we could just use a tuple with the first element being the current total and the second element being the current power:

fromDigits xs = fst $ foldr go (0, 1) xs
    where
        go digit (s, power) = (s + digit * power, power * 10)

This is roughly equivalent to the Python code

def fromDigits(digits):
    def go(digit, acc):
        s, power = acc
        return (s + digit * power, power * 10)

    state = (0, 1)
    for digit in digits:
        state = go(digit, state)
    return state[0]
bheklilr
  • 53,530
  • 6
  • 107
  • 163
0

Such a simple function can carry all its state in its bare arguments. Carry around an accumulator argument, and the operation becomes trivial.

fromDigits :: [Int] -> Integer
fromDigits xs = fromDigitsA xs 0  # 0 is the current accumulator value

fromDigitsA [] acc = acc
fromDigitsA (x:xs) acc = fromDigitsA xs (acc * 10 + toInteger x)
9000
  • 39,899
  • 9
  • 66
  • 104
0

If you're really determined to use a right fold for this, you can combine calculating length xs with the calculation like this (taking the liberty of defining fromDigits [] = 0):

fromDigits xn = let (x, _) = fromDigits' xn in x where
    fromDigits' [] = (0, 0)
    fromDigits' (x:xn) = (toInteger x * 10 ^ l + y, l + 1) where
        (y, l) = fromDigits' xn

Now it should be obvious that this is equivalent to

fromDigits xn = fst $ foldr (\ x (y, l) -> (toInteger x * 10^l + y, l + 1)) (0, 0) xn

The pattern of adding an extra component or result to your accumulator, and discarding it once the fold returns, is a very general one when you're re-writing recursive functions using folds.

Having said that, a foldr with a function that is always strict in its second parameter is a really, really bad idea (excessive stack usage, maybe a stack overflow on long lists) and you really should write fromDigits as a foldl as some of the other answers have suggested.

Jonathan Cast
  • 4,569
  • 19
  • 34
0

If you want to "fold with state", probably Traversable is the abstraction you're looking for. One of the methods defined in Traversable class is

traverse :: Applicative f => (a -> f b) -> t a -> f (t b)

Basically, traverse takes a "stateful function" of type a -> f b and applies it to every function in the container t a, resulting in a container f (t b). Here, f can be State, and you can use traverse with function of type Int -> State Integer (). It would build an useless data structure (list of units in your case), but you can just discard it. Here's a solution to your problem using Traversable:

import Control.Monad.State
import Data.Traversable

sumDigits :: Traversable t => t Int -> Integer
sumDigits cont = snd $ runState (traverse action cont) 0
  where action x = modify ((+ (fromIntegral x)) . (* 10))

test1 = sumDigits [1, 4, 5, 6]

However, if you really don't like building discarded data structure, you can just use Foldable with somewhat tricky Monoid implementation: store not only computed result, but also 10^n, where n is count of digits converted to this value. This additional information gives you an ability to combine two values:

import Data.Foldable
import Data.Monoid

data Digits = Digits
  { value :: Integer
  , power :: Integer
  }

instance Monoid Digits where
  mempty = Digits 0 1
  (Digits d1 p1) `mappend` (Digits d2 p2) =
    Digits (d1 * p2 + d2) (p1 * p2)

sumDigitsF :: Foldable f => f Int -> Integer
sumDigitsF cont = value $ foldMap (\x -> Digits (fromIntegral x) 10) cont

test2 = sumDigitsF [0, 4, 5, 0, 3]

I'd stick with first implementation. Although it builds unnecessary data structure, it's shorter and simpler to understand (as far as a reader understands Traversable).