Question

I'm trying to learn Haskll, and so I was trying out question 26 of Project Euler in Haskell: http://projecteuler.net/problem=26

My solution to the problem is this:

answer26 = answer26' 1000
answer26' n = snd $ maximum $ map (\x -> cycleLength x [1]) [2..n - 1]
    where
    cycleLength n (r:rs)
        | i /= Nothing      = (1 + fromJust i, n)
        | r < n             = cycleLength n $ (10*r):r:rs
        | otherwise         = cycleLength n $ (r `mod` n):r:rs
        where i = elemIndex r rs

I realize that this isn't the most efficient algorithm, but seeing as it's naively O(n^3) (where n = 1000) that is not such an issue. What I am concerned about though, is that from my understanding of monads, one of their main properties is that they in some sense "mark" anything that has used the monad. The function "fromJust" seems to fly directly in the face of that. Why does it exist? Also, assuming its existence is justified, is my usage of it in the above code good practice?

Was it helpful?

Solution

Usage of partial functions (functions that may not return a value) is generally discouraged. Functions like head and fromJust exist because they're occasionally convenient; you can sometimes write shorter code, which is more understandable to learners. Lots of functional algorithms are expressed in terms of head and tail and fromJust is conceptually the same as head.

It's usually preferable to use pattern matching, and to avoid partial functions, because it allows the compiler to catch errors for you. In your code snippet you have carefully checked that the value is never Nothing, but in large real-life codebases, code can be many years old, 1000's of lines long and maintained by many developers. It's very easy for a developer to re-order some code and miss out a check like that. With pattern-matching, it's right there in the code structure, not just in some arbitrary Bool expression.

It's not too difficult to replace your usage of fromJust with pattern-matching:

answer26 = answer26' 1000
answer26' n = snd $ maximum $ map (\x -> cycleLength x [1]) [2..n - 1]
    where
    cycleLength n (r:rs) = case elemIndex r rs of
            Just i  -> (1 + i, n)
            Nothing -> if r < n
                        then cycleLength n $ (10*r):r:rs
                        else cycleLength n $ (r `mod` n):r:rs

And (I think) the result is a bit clearer too.

Edit: There's an apparently "theoretically ok" place to use fromJust mentioned in Typeclassopedia, though you will need someone other than me to explain wtf that is all about.. ;)

OTHER TIPS

The monad interface doesn't include any specific function for "extracting" values from a monad, only for putting them in (return).

However, it doesn't forbid these kinds of functions either. When they exist, they will be specific to each monad (hence the multitude of run* functions: runIdentity, runReader, runWriter, runState... each with different arguments.)

By design, IO doesn't have any such "get out" function, and so it serves to "trap" impure values inside the monad. But "not being able to get out" is not a requirement for monads in general. What counts is that they respect the monad laws.

With comonads, the situation is reversed. There is a common function to extract values from them (extract) that every comonad must implement. But the functions to "put the values in", when they exist, vary for each particular comonad (env, store...)

As for fromJust, it is good practice to avoid it whenever possible because it is a partial function which may fail to match at runtime.

This pattern is so common, there is even a function for that: maybe :: b -> (a -> b) -> Maybe a -> b

In your case, if you do \x -> (cycleLength x [1], x), that is, construct the pair outside cycleLength:

    cycleLength n (r:rs) = maybe (cycleLength n rs') (1+) $ elemIndex r rs where
      rs'
        | r < n = (10*r):r:rs
        | otherwise = (r `mod` n):r:rs

Also, because you are looking just for a maximum, not the actual value, it will work even with id instead of (1+).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top