3

I'm using Haskell to find a list of integers from 1 to 10000 that have a special property. I do the following

[ number | number <- [1..10000], (isSpecial number)]

However, every now and then I came up with some special properties that are

  1. hard to be satisfied

  2. take a long time to be verified

As a result, it hangs there after some first few examples.

I wonder how I can make the list comprehension in Haskell verbose, so I have a good update about how much Haskell has progressed.

Student
  • 400
  • 3
  • 10
  • you can't make the list comprehension "verbose" in the way you suggest. You could get this information a totally different way though, by making an `IO` action that loops through all the numbers form 1 to 10000, checks each against `isSpecial` and prints out its progress to the terminal. Is that the kind of thing you're talking about? – Robin Zigmond Nov 25 '19 at 21:52
  • Hmm.. you mean I'll have to write a `isSpecial_Verbose` which says something everytime it is called? – Student Nov 25 '19 at 21:55
  • `isSpecial` is a function from `Int` to `Bool`. How do I make its output to be both `Bool` and `IO()`? @RobinZigmond – Student Nov 25 '19 at 21:56
  • 1
    I wonder if you do Project Euler tasks. In there, you usually have two choices: first, generate and filter -- exactly what you show in the post; second, generate only numbers satisfying the property in the first place. The second approach is trickier but perform much better than the first one. – Artem Pelenitsyn Nov 25 '19 at 22:00
  • No, I mean write a totally new value of type `IO ()` which uses `isSpecial` (and loop through the relevant numbers) – Robin Zigmond Nov 25 '19 at 22:01
  • @ArtemPelenitsyn some problems in math are just too hard that no one knows how to generate exactly the set of desired numbers. – Student Nov 25 '19 at 22:02
  • @RobinZigmond I still don't quite get it.. do you mean to write a function `verbosified :: (a->b) -> IO()`, and apply it to `isSpecial`? My question is still there: I need the function `isSpecial` to report its boolean results. How can I keep this while making it verbose? – Student Nov 25 '19 at 22:05
  • well I can show you if you want, just didn't want to write an answer yet without knowing if this was what you were looking for. But what I mean is you can do, for example `tellMeResult :: Int -> IO (); tellMeResult n = putStrLn $ "Testing " ++ show n ++ " - it's " ++ (if isSpecial n then "special" else "not special")`. Then run that over all the numbers 1 to 1000 (eg with `mapM`). – Robin Zigmond Nov 25 '19 at 22:13
  • What I'm suggesting isn't particularly sophisticated, but it will give you a crude view of how long the calculation is taking for each number, and where you're up to – Robin Zigmond Nov 25 '19 at 22:14
  • Yes, then I get it! However, I asked this because I still need the comprehension list there. Usually comprehension list is great.. but sometimes the system hang because there is one list that takes so much time. If I could make all list verbose, I can spot what eats up time the most and fix it more efficiently! – Student Nov 25 '19 at 22:17
  • You can use [trace](https://hackage.haskell.org/package/base-4.12.0.0/docs/Debug-Trace.html#v:trace) to print current state to console while it is evaluated – Ed'ka Nov 26 '19 at 01:29

4 Answers4

3

This is more or less what Robin Zigmond meant:

checkNumbers :: IO [Int]
checkNumbers = filterM check [1..10000]
    where
        check number = do
            print $ "Checking number" <> show number
            pure $ isSpecial number

This will print "Checking number x" before checking every number. Feel free to experiment with any other effects (or, in your words, "verbosity") within the check function.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172
  • I'm still understanding this code. I haven't seen `IO [Int]`, and are not familiar with `pure`. I will get back to this after learning them.. – Student Nov 25 '19 at 22:35
  • 1
    @Student I expect you've seen `return`; if you have, `pure` is the same (it applies on more types, but don't worry about that for now). As for `IO [Int]`, `IO` is a type constructor, and `IO a` basically means "this is an action that performs some input/output and returns a value of type `a`". `a` can be anything, including a list as here. – Robin Zigmond Nov 25 '19 at 22:49
1

Here is a way that requires no IO, instead relying on laziness and your programmer guess about which "side" of the condition happens more often. Just to have something to play with, here's a slightly slow function that checks if a number is a multiple of 10. The details of this function aren't important, feel free to skip it if anything doesn't make sense. I'm also going to turn on timing reporting; you'll see why later.

> isSpecial :: Int -> Bool; isSpecial n = last [1..10000000] `seq` (n `mod` 10 == 0)
> :set +s

(Add one 0 every five years.)

Now the idea will be this: instead of your list comprehension, we'll use partition to split the list into two chunks, the elements that match the predicate and the ones that don't. We'll print the one of those that has more elements, so we can keep an eye on progress; by the time it's fully printed, the other one will be fully evaluated and we can inspect it however we like.

> :m + Data.List
> (matches, nonMatches) = partition isSpecial [1..20]
(0.00 secs, 0 bytes)
> nonMatches
[1,2,3,4,5,6,7,8,9,11,12,13,14,15,16,17,18,19]
(12.40 secs, 14,400,099,848 bytes)

Obviously I can't portray this over StackOverflow, but when I did the above thing, the numbers in the nonMatches list slowly appeared on my terminal one-by-one, giving a pretty good indicator of where in the list it was currently thinking. And now, when you print matches, the full list is available more or less instantly, as you can see by the timing report (i.e. not another 12-second wait):

> matches
[10,20]
(0.01 secs, 64,112 bytes)

But beware!

  1. It's important that matches and nonMatches have types which are not typeclass polymorphic (i.e. don't have types that start with Num a => ... or some other constraint). In the above example, I achieved this by making isSpecial monomorphic, which forces matches and nonMatches to be, too, but if your isSpecial is polymorphic, you should give a type signature for matches or nonMatches to prevent this problem.

  2. Doing it this way will cause the entire nonMatches and matches lists to be realized in memory. This could be expensive if the original list being partitioned is very long. (But up to, say, a couple hundred thousand Ints is not particularly long for modern computers.)

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
1

Debug.Trace

You can have a look at Debug.Trace. It allows printing messages to the console. But as Haskell is lazy, controlling when printing happens is not so easy. And this is also not recommended for production:

Prelude Debug.Trace> import Debug.Trace
Prelude Debug.Trace> [x | x <- [1..10], traceShow (x, odd x) $ odd x]
(1,True)
[1(2,False)
(3,True)
,3(4,False)
(5,True)
,5(6,False)
(7,True)
,7(8,False)
(9,True)
,9(10,False)
]
Community
  • 1
  • 1
ziggystar
  • 28,410
  • 9
  • 72
  • 124
0

We would usually want to see both the tried and the discovered numbers as the calculation goes on.

What I usually do is break up the input list into chunks of n elements, filter each chunk as you would the whole list, and convert each chunk into a pair of its head element and the filtered chunk:

chunked_result = [ (h, [n | n <- chunk, isSpecial n])
                   | chunk@(h:_) <- chunksOf n input]

Putting such result list through concatMap snd gives the original non-"verbose" option.

Adjusting the n value will influence the frequency with which the progress will be "reported" when the result list is simply printed, showing both the tried and the discovered numbers, with some inconsequential "noise" around them.

Using second concat . unzip on the chunks results list is somewhat similar to Daniel Wagner's partitioning idea (with caveats),(*) but with your set value of n, not just 1.

If there is an algorithmic slowdown innate to your specific problem, apply the orders of growth run time estimation analysis.


(*) to make it compatible we need to stick some seq in the middle somewhere, like

chunked_result = [ (last s `seq` last chunk, s)
                   | chunk <- chunksOf n input
                     let s = [n | n <- chunk, isSpecial n] ]

or something.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • It's nice, but `second concat . unzip` won't quite recapture what my answer does: none of the calls to `isSpecial` will be forced by the printing of the `fst` part of the result, so the `fst` part doesn't actually give a good indicator of how much progress has been made. That's fine -- this is just a *different thing*, and there's no need to exactly reproduce what I did with it. But I'd recommend just dropping that part of the answer to avoid confusing things. – Daniel Wagner Nov 26 '19 at 21:00
  • I've edited that part. repeating your idea was not the goal of what I wrote of course, this chunking trick is something that I often do myself, probably with some seq in the middle (as you point out -- will edit that in shortly). I mostly just print the results as is, so everything *is* forced. I even initially wrote "what I often do in such situations is ..." but then edited for neutrality. it just occurred to me that it is the same thing so I mentioned it. – Will Ness Nov 27 '19 at 09:19