[Haskell-beginners] Re: [Haskell-cafe] try, seq, and IO

Jeroen van Maanen jeroen at lexau.org
Thu Sep 16 04:54:33 EDT 2010


Op 2010-09-16 08:50, Jeroen van Maanen schreef:
> Op 2010-09-15 18:38, Neil Brown schreef:
>> On 15/09/10 10:13, Jeroen van Maanen wrote:
>>> The past year I have been working on a port of my machine learning project named LExAu from Java to Haskell. I'm still very glad I took the jump, because the complexity curve appears to be log shaped rather than exp shaped. In one year I almost got to the functionality that had taken me five years to produce in Java (of course it helped a lot that I had a working prototype this time).
>>>
>>> There is one thing that still bothers me though: when I write seq or $! it doesn't seem to have any effect!
>>>
>>> Currently I am trying to add some exception handling to help me debug the system, but the code that I managed to produce depends on the logging statement to produce the desired result. :-( It looks like this, and only works when I uncomment the line '-- logger "Check sum": [...]', otherwise the exception is caught by the try around the body of the thread that this code runs in:
>>>
>>>           do logger "Received update" [showString label, logs update]
>>>              result<-
>>>                try $!
>>>                  do maybeUpdatedModel<- return $ f update startModel
>>>                     theCheckSum<- return $ liftM checkSum maybeUpdatedModel
>>> --                   logger "Check sum" [showString label, shows theCheckSum]
>>>                     return $! seq theCheckSum maybeUpdatedModel
>>>              maybeNextModel<-
>>>                case result of
>>>                  Right theMaybeNextModel ->  return theMaybeNextModel
>>>                  Left exception ->
>>>                    do let exc :: SomeException
>>>                           exc = exception
>>>                       logger "Exception" [showString label, shows exception]
>>>                       return Nothing
>>>              logger "Maybe next model" [showString label, logs maybeNextModel]
>>>
>>> For more context see:
>>>
>>>    http://lexau.svn.sourceforge.net/viewvc/lexau/branches/totem/src/LExAu/Pipeline/Concurrent.hs?revision=326&view=markup
>>>
>>> after line 241.
>>>
>>> Can someone explain why a few showStrings a shows and a putStrLn are more effective in forcing the check sum to be computed (which necessarily evaluates the complete updated model and reveals the lurking exception) than the seq on the line just below the logging statement?
>>>    
>> I just looked at your code for a second time and I think I know.  Here's my guess:
>>
>> It's all about the types, and weak head normal form.  The "theCheckSum" item has type Maybe a, and you are looking for an exception in the "a" part that's wrapped by Maybe (I presume: the Just/Nothing is determined by the function "f", not "checkSum").  When you use seq or $!, you only evaluated to weak head normal form,  For something of type "Maybe a", that's only the Just/Nothing part.  Forcing that aspect to be evaluated only requires some evaluation of the function "f", and the checkSum function doesn't need to be applied, even if it is a Just value.  However, your shows function digs deeper and evaluates the whole thing, so hence it will trigger the error where seq and $! won't.
> 
> Ah, I didn't know that weak normal form evaluation doesn't force the evaluation of the argument of the Just constructor. That makes sense. (I confess that I added the liftM construction almost without thinking when the compiler told me that I couldn't just ask for the checkSum of maybeUpdatedModel and had not realized that this changed the type of theCheckSum. I also stubbornly ignored the telltale "Just" in the log statement.)
> 
>> So, if I'm right, you need to change your function to make sure to evaluate the checksum itself.  The deepseq package on Hackage may help here (use the deepseq function in place of seq: http://hackage.haskell.org/packages/archive/deepseq/1.1.0.0/doc/html/Control-DeepSeq.html).
> 
> The maybe function does the trick quite neatly. In fact, I added the checkSum method because my particular data stuctures have quite natural Integer valued functions that force evaluation of the complete, ... erm, in Java I would have said object graph, ... you know, ... everything. ;-)
> 
>> I'm a little suspicious of the code as a whole though: why does checkSum throw an exception rather than returning a Maybe value?  Or is it an exception somehow outside of your control?
> 
> That is because the exception is thrown by an assert kind of thing. There are still bugs lurking in the code (strange, I have been so careful ;-> ), but I don't want to litter my code with Maybe types just in case there Maybe something wrong with it. I use Maybe frequently, for example, when a function may or may not return an updated data structure when given some extra information, but in those cases it is natural in the context of the algorithm to use a Maybe type.
> 
>> Another thing:
>>
>> x <- return $ blah
>>
>> in a do block is bad style, and can always be replaced with:
>>
>> let x = blah
>>
>> Note that there is no "in" needed with let statements in do blocks (that confused me when first learning Haskell).
> 
> That's funny, first I fixed my code:
> 
>                 do let maybeUpdatedModel = f update startModel
>                    theCheckSum <- return $ maybe 0 checkSum maybeUpdatedModel
>                    return $! seq theCheckSum maybeUpdatedModel
> 
> That worked beautifully. The exception is caught here as desired. Then I tried to get rid of the remaining '<- return'. However, when I write:
> 
>                 do let maybeUpdatedModel = f update startModel
>                        theCheckSum = maybe 0 checkSum maybeUpdatedModel
>                    return $! seq theCheckSum maybeUpdatedModel
> 
> It reverts to the old behavior. What is the subtle difference here?
> 
> And let me guess, the next step is to replace
> 
>   do let blah
>          blah
>      return result
> 
> by
> 
>   return $ let blah
>                blah
>            in result
> 
> Right?

Gregs suggestion provided the way out: evaluate. My code now looks like this:

         do let maybeUpdatedModel = f update startModel
                theCheckSum = maybe 0 checkSum maybeUpdatedModel
                logException exception = logger "Exception" [showString label, shows (exception :: SomeException)] >> return Nothing
            logger "Received update" [showString label, logs update]
            resultOrException <- try $ evaluate $ seq theCheckSum maybeUpdatedModel
            maybeNextModel <- either logException return resultOrException
            logger "Maybe next model" [showString label, logs maybeNextModel]
            -- ...

I still don't understand why evaluating maybeUpdatedModel to WHNF after seq evaluated theCheckSum to WHNF forces the lurking exception to the surface. Or is the phrase 'when the resultant IO action is executed' in the documentation of 'evaluate' significant here?

Cheers, Jeroen



More information about the Beginners mailing list