packages feed

error-message 1.0.1 → 1.1

raw patch · 4 files changed

+279/−43 lines, 4 filesdep ~InfixApplicativedep ~ansi-wl-pprintdep ~basesetup-changedPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: InfixApplicative, ansi-wl-pprint, base, containers, either-unwrap, mtl

API changes (from Hackage documentation)

- Data.ErrorMessage: instance Monoid Doc
+ Data.ErrorMessage: type ErrorMessageOr = Either ErrorMessage

Files

Data/ErrorMessage.hs view
@@ -1,5 +1,11 @@+-- @+leo-ver=4-thin+-- @+node:gcross.20091202203048.1603:@thin ErrorMessage.hs+-- @@language Haskell+-- @@raw ------------------------------------------------------------------------------ -- |+-- @@end_raw+-- @+at --  Module      :  Data.ErrorMessage --  Copyright   :  (c) Gregory Crosswhite --  License     :  BSD-style@@ -16,6 +22,9 @@ --   --  The following provides an example of how these can be used: --  +-- @-at+-- @@c+-- @@raw -- > sqrtWithError :: Float -> Either ErrorMessage Float -- > sqrtWithError x -- >     | x < 0@@ -36,6 +45,8 @@ -- >     case sumWithError (sqrtWithError x) (sqrtWithError y) of -- >         Right value -> "The value is " ++ show value -- >         Left error -> show . formatErrorMessage $ error+-- @@end_raw+-- @+at --   --  The result of @showSumOrErrorOf (-1) (-2)@ is the string, --  @@ -88,14 +99,22 @@ --   --  Now both error messages are displayed. --  +-- @-at+-- @@c+-- @@raw ------------------------------------------------------------------------------+-- @@end_raw -{-# LANGUAGE GeneralizedNewtypeDeriving #-}+-- @<< Language extensions >>+-- @+node:gcross.20091202203048.7000:<< Language extensions >>+-- @-node:gcross.20091202203048.7000:<< Language extensions >>+-- @nl  module Data.ErrorMessage   (     -- * The ErrorMessage Type      ErrorMessage(..)+    ,ErrorMessageOr     -- $error_message_type      -- ** Instances for ErrorMessage@@ -135,6 +154,8 @@     ,gatherResultsOrError   ) where +-- @<< Import needed modules >>+-- @+node:gcross.20091202203048.1605:<< Import needed modules >> import Control.Arrow import Control.Applicative hiding (empty) import Control.Applicative.Infix@@ -150,8 +171,20 @@ import qualified Data.Map as Map  import Text.PrettyPrint.ANSI.Leijen+-- @-node:gcross.20091202203048.1605:<< Import needed modules >>+-- @nl +-- @+others+-- @+node:gcross.20091202203048.1606:Types+-- @+node:gcross.20091202203048.1607:ErrorMessage newtype ErrorMessage = ErrorMessage { unwrapErrorMessage :: Map String Doc }+type ErrorMessageOr = Either ErrorMessage+-- @-node:gcross.20091202203048.1607:ErrorMessage+-- @-node:gcross.20091202203048.1606:Types+-- @+node:gcross.20091202203048.1608:Instances+-- @+node:gcross.20091202203048.1609:Applicative (Either e a)+-- @@raw+-- @@end_raw  instance (Monoid e) => Applicative (Either e) where     pure = Right@@ -159,54 +192,92 @@     (<*>) (Left error) _ = Left error     (<*>) _ (Left error) = Left error     (<*>) (Right function) (Right argument) = Right (function argument)+-- @-node:gcross.20091202203048.1609:Applicative (Either e a)+-- @+node:gcross.20091202203048.1610:Applicative (ErrorT e m a) instance (Monoid e, Error e, Monad m) => Applicative (ErrorT e m) where     pure = return     e_fn <*> e_arg = ErrorT $ liftM2 (<*>) (runErrorT e_fn) (runErrorT e_arg)+-- @-node:gcross.20091202203048.1610:Applicative (ErrorT e m a)+-- @+node:gcross.20091202203048.1611:Error ErrorMessage instance Error ErrorMessage where     noMsg = strMsg "(and he did not even bother to include an error message!  :-/)"     strMsg = errorMessage "Error caused by the programmer:" . text+-- @-node:gcross.20091202203048.1611:Error ErrorMessage+-- @+node:gcross.20091202203048.7025:Monoid ErrorMessage instance Monoid ErrorMessage where     mempty = ErrorMessage Map.empty     mappend (ErrorMessage a) (ErrorMessage b) = ErrorMessage (mappend a b)     mconcat = ErrorMessage . mconcat . map unwrapErrorMessage+-- @-node:gcross.20091202203048.7025:Monoid ErrorMessage+-- @+node:gcross.20091202203048.1613:Error Doc instance Error Doc where     noMsg = empty     strMsg = text-instance Monoid Doc where-    mempty = empty-    mappend = (<$$>)-    mconcat = vcat+-- @-node:gcross.20091202203048.1613:Error Doc+-- @+node:gcross.20091202203048.1612:Monoid Doc+-- @-node:gcross.20091202203048.1612:Monoid Doc+-- @-node:gcross.20091202203048.1608:Instances+-- @+node:gcross.20091202203048.1614:Functions+-- @+node:gcross.20091202203048.7038:Error Message Creation+-- @+node:gcross.20091202203048.1619:errorMessage / leftErrorMessage+-- @@raw -- |+-- @@end_raw+-- @+at --  The function 'errorMessage' takes a heading and a body and produce an  --  ErrorMessage object from them;  this can be considered to be a thin  --  wrapper around 'Data.Map.singleton'.+-- @-at+-- @@c  errorMessage :: String -> Doc -> ErrorMessage errorMessage heading = ErrorMessage . Map.singleton heading +-- @@raw -- |+-- @@end_raw+-- @+at --  Since one usually wants to return not just an ErrorMessage, but a value of  --  the form @Left error_message@, the function 'leftErrorMessage' is provided  --  as a convenience;  it creates the error message, and then wraps it inside  --  of 'Left'.+-- @-at+-- @@c  leftErrorMessage :: String -> Doc -> Either ErrorMessage a leftErrorMessage heading = Left . errorMessage heading+-- @-node:gcross.20091202203048.1619:errorMessage / leftErrorMessage+-- @+node:gcross.20091202203048.1620:errorMessageText / leftErrorMessageText+-- @@raw -- |+-- @@end_raw+-- @+at --  The function 'errorMessageText' is similar to the function 'errorMessage',  --  but for the body it takes a 'String' instead of a 'Doc'.  It is provided  --  for convenience.+-- @-at+-- @@c  errorMessageText :: String -> String -> ErrorMessage errorMessageText heading = errorMessage heading . text  +-- @@raw -- |+-- @@end_raw+-- @+at --  The function 'leftErrorMessageText' is 'errorMessageText' composed with  --  the 'Left' constructor for convenience.+-- @-at+-- @@c leftErrorMessageText :: String -> String -> Either ErrorMessage a leftErrorMessageText heading = Left . errorMessageText heading+-- @-node:gcross.20091202203048.1620:errorMessageText / leftErrorMessageText+-- @+node:gcross.20091202203048.1621:errorMessageTextFromMultilineString / leftErrorMessageTextFromMultilineString+-- @@raw -- |+-- @@end_raw+-- @+at --  Use this function when you want to create an error message from a  --  multi-line string. --  @@ -227,7 +298,12 @@ --  'errorMessageTextFromMultilineString' takes care of this for you.  For  --  example, --  +-- @-at+-- @@c+-- @@raw -- > errorMessageTextFromMultilineString "A poem:" "Roses are red.\nViolets are blue."+-- @@end_raw+-- @+at --   --  produces the following (formatted) error message: --  @@ -235,22 +311,38 @@ --  >     Roses are red. --  >     Violets are blue. --  +-- @-at+-- @@c  errorMessageTextFromMultilineString :: String -> String -> ErrorMessage errorMessageTextFromMultilineString heading = errorMessage heading . vcat . map text . lines +-- @@raw -- |+-- @@end_raw+-- @+at --  The function 'leftErrorMessageTextFromMultilineString' is  --  'errorMessageTextFromMultilineString' composed with the 'Left' constructor  --  for convenience.+-- @-at+-- @@c  leftErrorMessageTextFromMultilineString :: String -> String -> Either ErrorMessage a leftErrorMessageTextFromMultilineString heading = Left . errorMessageTextFromMultilineString heading+-- @-node:gcross.20091202203048.1621:errorMessageTextFromMultilineString / leftErrorMessageTextFromMultilineString+-- @-node:gcross.20091202203048.7038:Error Message Creation+-- @+node:gcross.20091202203048.7039:Formatting+-- @+node:gcross.20091202203048.1617:formatErrorMessage+-- @@raw -- |+-- @@end_raw+-- @+at --  This function takes an 'ErrorMessage' and formats it into a 'Doc'.  It  --  does this by converting the headings into 'text' objects, merging them  --  with their respective bodies (the latter having been indented by four  --  spaces), and then concatenating the result.+-- @-at+-- @@c  formatErrorMessage :: ErrorMessage -> Doc formatErrorMessage =@@ -261,26 +353,47 @@     Map.assocs     .     unwrapErrorMessage+-- @-node:gcross.20091202203048.1617:formatErrorMessage+-- @+node:gcross.20091202203048.1618:formatMessageWithHeading+-- @@raw -- |+-- @@end_raw+-- @+at --  This is the utility function used by 'formatErrorMessage' to format a  --  'Doc' given a heading and a body;  it indents the body by four spaces and  --  then appends it after the heading.+-- @-at+-- @@c  formatMessageWithHeading :: String -> Doc -> Doc formatMessageWithHeading heading body =     text heading     <$$>     indent 4 body+-- @-node:gcross.20091202203048.1618:formatMessageWithHeading+-- @-node:gcross.20091202203048.7039:Formatting+-- @+node:gcross.20091202203048.7040:Extracting results from a list+-- @+node:gcross.20091202203048.1616:gatherResultsOrErrors+-- @@raw -- |+-- @@end_raw+-- @+at --  This function takes a list of values which might contain errors and  --  returns either a list of the errors found in the values or the full list  --  of results.  Note that there is no restriction on the type of the errors.+-- @-at+-- @@c gatherResultsOrErrors :: [Either e a] -> Either [e] [a] gatherResultsOrErrors eithers =     case partitionEithers (eithers) of         ([],results) -> Right results         (errors,_) -> Left errors+-- @-node:gcross.20091202203048.1616:gatherResultsOrErrors+-- @+node:gcross.20091202203048.1615:gatherResultsOrError+-- @@raw -- |+-- @@end_raw+-- @+at --  This function is similar to 'gatherResultsOrErrors', but instead of  --  returning a list of errors it combines them into a single error.  Note  --  that only restriction on the type of the error is that it be an instance @@ -314,10 +427,23 @@ --  >     apple --  >     cat --  +-- @-at+-- @@c gatherResultsOrError :: Monoid e => [Either e a] -> Either e [a] gatherResultsOrError = mapLeft mconcat . gatherResultsOrErrors+-- @-node:gcross.20091202203048.1615:gatherResultsOrError+-- @-node:gcross.20091202203048.7040:Extracting results from a list+-- @-node:gcross.20091202203048.1614:Functions+-- @-others +-- @<< Documentation sections >>+-- @+node:gcross.20091202203048.7003:<< Documentation sections >>+-- @+others+-- @+node:gcross.20091202203048.7004:ErrorMessage type+-- @@raw -- $error_message_type+-- @@end_raw+-- @+at --  The 'ErrorMessage' type is simply a map from 'String' to 'Doc'; the reason  --  why the values are 'Doc' is because this allows us to compose them using  --  the combinators in Leijen's pretty-printing library.@@ -328,10 +454,16 @@ --  such an assumption, then we would have no way of preventing the same error  --  from appearing several times in the message in the case that many  --  sub-computations all depended on the same erroneous result.+-- @-at+-- @@c   +-- @+node:gcross.20091202203048.7027:ErrorMessage instances+-- @@raw -- $error_message_instances+-- @@end_raw+-- @+at --  In some respects, the most important part of the 'ErrorMessage' type are  --  its instances: --  @@ -353,9 +485,16 @@ --  when there is a pattern match failure.  Thus, the heading of errors  --  created by 'noMsg' and 'strMsg' is --  /Error caused by the programmer:/+-- @-at+-- @@c  +-- @-node:gcross.20091202203048.7027:ErrorMessage instances+-- @+node:gcross.20091202203048.7029:Doc instances+-- @@raw -- $doc_instances+-- @@end_raw+-- @+at --  Unfortunately, it does not show up in the API documentation that this  --  module also defines the following two instances for 'Doc': --  @@ -365,13 +504,25 @@ --  * The 'Error' instance defines a 'noMsg' error to be the 'empty' 'Doc' and  --  the 'strMsg' error to be a 'text' 'Doc'. --  +-- @-at+-- @@c  +-- @-node:gcross.20091202203048.7029:Doc instances+-- @+node:gcross.20091202203048.7031:Applicative instances+-- @@raw -- $applicative_instances+-- @@end_raw+-- @+at --  Unfortunately, it does not show up in the API documentation that this  --  module also defines the following two 'Applicative' instances: --  +-- @-at+-- @@c+-- @@raw -- > instance (Monoid e) => Applicative (Either e) where ...+-- @@end_raw+-- @+at --   --  This instance declaration allows you to lift pure functions into functions  --  that work with values that might have errors, since both 'Doc' and @@ -383,9 +534,14 @@ --  present, whereas the latter will ignore errors in the second argument if  --  there is an error in the first argument. --  +-- @-at+-- @@c+-- @@raw -- > instance (Monoid e, Error e, Monad m) => Applicative (ErrorT e m) where -- >     pure = return -- >     e_fn <*> e_arg = ErrorT $ liftM2 (<*>) (runErrorT e_fn) (runErrorT e_arg)+-- @@end_raw+-- @+at --   --  This instance definition lifts the @Applicative (Either e)@ so that it  --  works for values obtained from monadic computations.  Note that the @@ -396,29 +552,64 @@ --   --  These instances allow you to write code like the following: --  +-- @-at+-- @@c+-- @@raw -- > data Point = Point { x :: Int, y :: Int, z :: Int } -- > -- > pOrError = Point <$> xOrError <*> yOrError <*> zOrError+-- @@end_raw+-- @+at --   --  The value @pOrError@ is either a @Point@, or a combination of the error  --  messages in @xOrError@, @yOrError@, and @zOrError@. --  +-- @-at+-- @@c  +-- @-node:gcross.20091202203048.7031:Applicative instances+-- @-node:gcross.20091202203048.7004:ErrorMessage type+-- @+node:gcross.20091202203048.7032:Creating Error Messages+-- @@raw -- $error_message_creation+-- @@end_raw+-- @+at --  Up to now we have spent a lot time discussing how to combine  --  'ErrorMessage's, but little time discussing how to produce them.  The  --  provided functions for doing this are as follows:+-- @-at+-- @@c +-- @-node:gcross.20091202203048.7032:Creating Error Messages+-- @+node:gcross.20091202203048.7042:Formatting Error Messages+-- @@raw -- $error_message_formatting+-- @@end_raw+-- @+at --  The end purpose of 'ErrorMessage' \'s existence is to be displayed to the  --  user.  Towards this end, the following functions format an 'ErrorMessage'  --  into a 'Doc'.+-- @-at+-- @@c +-- @-node:gcross.20091202203048.7042:Formatting Error Messages+-- @+node:gcross.20091202203048.7044:Gathering Results with Errors+-- @@raw -- $gathering_results_with_errors+-- @@end_raw+-- @+at --  Although there are many combinators available (such as 'liftA' and \<$\>)  --  for lifting pure functions to functions that handle errors, there are  --  times when one wants to gather together a list of results which might  --  possibly contain some errors.  The following functions assist in doing  --  this.+-- @-at+-- @@c +-- @-node:gcross.20091202203048.7044:Gathering Results with Errors+-- @-others+-- @-node:gcross.20091202203048.7003:<< Documentation sections >>+-- @nl+-- @-node:gcross.20091202203048.1603:@thin ErrorMessage.hs+-- @-leo
Setup.hs view
@@ -1,3 +1,5 @@+-- @+leo-ver=4-thin+-- @+node:gcross.20091202203048.6991:@thin Setup.hs import Control.Monad import Distribution.Simple import System.Exit@@ -22,3 +24,5 @@   putStrLn ""   system "runhaskell -i. -i./tests tests/runtests.hs"   return ()+-- @-node:gcross.20091202203048.6991:@thin Setup.hs+-- @-leo
error-message.cabal view
@@ -1,10 +1,13 @@+-- @+leo-ver=4-thin+-- @+node:gcross.20091202203048.1623:@thin error-message.cabal+-- @@language Haskell Name:           error-message-Version:        1.0.1+Version:        1.1 Cabal-Version:  >= 1.6 License:        BSD3 License-File:   LICENSE Author:         Gregory Crosswhite-Copyright:      (c) 2009 Gregory Crosswhite+Copyright:      (c) 2009-2010 Gregory Crosswhite Maintainer:     gcross@phys.washington.edu Stability:      stable Homepage:       http://github.com/gcross/error-message@@ -18,14 +21,19 @@     encountered.  Towards this end, this module supplies a type of     /combinable error messages/ so that all of the errors from subcomputations     can be gathered and presented together.+    .+    New in version 1.1:  Removed Monoid instance for Doc (it should now be supplied by ansi-wl-pprint), added ErrorMessageOr type alias. Extra-Source-Files: tests/runtests.hs  Library     Exposed:            True     Exposed-Modules:    Data.ErrorMessage-    Build-Depends:      base == 4.*,-                        mtl == 1.*,-                        containers == 0.2.*,-                        either-unwrap >= 1.1 && < 2,-                        InfixApplicative == 1.*,-                        ansi-wl-pprint == 0.5.*+    Build-Depends:      base >= 4.1 && < 4.3,+                        mtl >= 1.1 && < 1.2,+                        containers >= 0.2 && < 0.4,+                        either-unwrap >= 1.1 && < 1.2,+                        InfixApplicative >= 1.0 && < 1.2,+                        ansi-wl-pprint >= 0.5 && < 0.6.4+-- @nonl+-- @-node:gcross.20091202203048.1623:@thin error-message.cabal+-- @-leo
tests/runtests.hs view
@@ -1,3 +1,9 @@+-- @+leo-ver=4-thin+-- @+node:gcross.20091202203048.6990:@thin runtests.hs+-- @@language Haskell++-- @<< Import needed modules >>+-- @+node:gcross.20091202203048.6992:<< Import needed modules >> import Control.Applicative import Control.Monad @@ -10,12 +16,21 @@ import Test.HUnit  import Text.PrettyPrint.ANSI.Leijen+-- @-node:gcross.20091202203048.6992:<< Import needed modules >>+-- @nl +-- @+others+-- @+node:gcross.20091202203048.7047:Values+-- @+node:gcross.20091202203048.7049:dictionary_mapping_words_to_lengths dictionary_mapping_words_to_lengths :: [(String,Int)] dictionary_mapping_words_to_lengths =     [("foo",3)     ,("bar",3)     ]+-- @-node:gcross.20091202203048.7049:dictionary_mapping_words_to_lengths+-- @-node:gcross.20091202203048.7047:Values+-- @+node:gcross.20091202203048.6995:Functions+-- @+node:gcross.20091202203048.7002:sqrtWithError sqrtWithError :: Float -> Either ErrorMessage Float sqrtWithError x     | x < 0@@ -24,58 +39,73 @@             "Square roots cannot be taken of negative numbers."     | otherwise         = Right (sqrt x)+-- @nonl+-- @-node:gcross.20091202203048.7002:sqrtWithError+-- @+node:gcross.20091202203048.7013:sumWithError+-- @+node:gcross.20091202203048.7001:sumWithError sumWithError :: Either ErrorMessage Float -> Either ErrorMessage Float -> Either ErrorMessage Float sumWithError (Left error1) (Left error2) = Left (error1 `mappend` error2) sumWithError (Left error) _ = Left error sumWithError _ (Left error) = Left error sumWithError (Right value1) (Right value2) = Right (value1 + value2) +-- @-node:gcross.20091202203048.7001:sumWithError+-- @+node:gcross.20091202203048.7006:sumWithError_2 sumWithError_2 argument1 argument2 = do     value1 <- argument1     value2 <- argument2     return (value1 + value2)+-- @-node:gcross.20091202203048.7006:sumWithError_2+-- @+node:gcross.20091202203048.7012:sumWithError_3 sumWithError_3 = liftM2 (+)+-- @-node:gcross.20091202203048.7012:sumWithError_3+-- @+node:gcross.20091202203048.7020:sumWithError_4 sumWithError_4 :: Either ErrorMessage Float -> Either ErrorMessage Float -> Either ErrorMessage Float sumWithError_4 = liftA2 (+)+-- @-node:gcross.20091202203048.7020:sumWithError_4+-- @-node:gcross.20091202203048.7013:sumWithError+-- @+node:gcross.20091202203048.7014:showSumOrErrorOf+-- @+node:gcross.20091202203048.6997:showSumOrErrorOf showSumOrErrorOf :: Float -> Float -> String showSumOrErrorOf x y =     case sumWithError (sqrtWithError x) (sqrtWithError y) of         Right value -> "The value is " ++ show value         Left error -> show . formatErrorMessage $ error+-- @nonl+-- @-node:gcross.20091202203048.6997:showSumOrErrorOf+-- @+node:gcross.20091202203048.7016:showSumOrErrorOf_2 showSumOrErrorOf_2 :: Float -> Float -> String showSumOrErrorOf_2 x y =     case sumWithError_2 (sqrtWithError x) (sqrtWithError y) of         Right value -> "The value is " ++ show value         Left error -> show . formatErrorMessage $ error+-- @-node:gcross.20091202203048.7016:showSumOrErrorOf_2+-- @+node:gcross.20091202203048.7018:showSumOrErrorOf_3 showSumOrErrorOf_3 :: Float -> Float -> String showSumOrErrorOf_3 x y =     case sumWithError_3 (sqrtWithError x) (sqrtWithError y) of         Right value -> "The value is " ++ show value         Left error -> show . formatErrorMessage $ error+-- @-node:gcross.20091202203048.7018:showSumOrErrorOf_3+-- @+node:gcross.20091202203048.7024:showSumOrErrorOf_4 showSumOrErrorOf_4 :: Float -> Float -> String showSumOrErrorOf_4 x y =     case sumWithError_4 (sqrtWithError x) (sqrtWithError y) of         Right value -> "The value is " ++ show value         Left error -> show . formatErrorMessage $ error-lookupAndReturnResultOrError :: String -> Either Doc Int-lookupAndReturnResultOrError word =-    case lookup word dictionary_mapping_words_to_lengths of-        Nothing -> Left (text word)-        Just word_length -> Right word_length-getWordLengthsOrError :: [String] -> Either ErrorMessage [Int]-getWordLengthsOrError =-    mapLeft-        (errorMessage-            "Error looking up the following words in the dictionary:"-        )-    .-    gatherResultsOrError-    .-    map lookupAndReturnResultOrError-+-- @-node:gcross.20091202203048.7024:showSumOrErrorOf_4+-- @-node:gcross.20091202203048.7014:showSumOrErrorOf+-- @-node:gcross.20091202203048.6995:Functions+-- @-others  main = defaultMain $+    -- @    << Tests >>+    -- @+node:gcross.20091202203048.6996:<< Tests >>+    -- @+others+    -- @+node:gcross.20091202203048.6993:Documentation examples     [testGroup "Documentation examples"+        -- @    @+others+        -- @+node:gcross.20091202203048.6994:showSumOrErrorOf         [testGroup "showSumOrErrorOf" $             [testCase "(-1) (-2)" $                 assertEqual@@ -100,6 +130,8 @@                     $                     showSumOrErrorOf (-1) (-1)             ]+        -- @-node:gcross.20091202203048.6994:showSumOrErrorOf+        -- @+node:gcross.20091202203048.7008:showSumOrErrorOf_2         ,testGroup "showSumOrErrorOf_2" $             [testCase "(-1) (-2)" $                 assertEqual@@ -112,6 +144,8 @@                     $                     showSumOrErrorOf_2 (-1) (-2)             ]+        -- @-node:gcross.20091202203048.7008:showSumOrErrorOf_2+        -- @+node:gcross.20091202203048.7010:showSumOrErrorOf_3         ,testGroup "showSumOrErrorOf_3" $             [testCase "(-1) (-2)" $                 assertEqual@@ -124,6 +158,8 @@                     $                     showSumOrErrorOf_3 (-1) (-2)             ]+        -- @-node:gcross.20091202203048.7010:showSumOrErrorOf_3+        -- @+node:gcross.20091202203048.7022:showSumOrErrorOf_4         ,testGroup "showSumOrErrorOf_3" $             [testCase "(-1) (-2)" $                 assertEqual@@ -138,20 +174,8 @@                     $                     showSumOrErrorOf_4 (-1) (-2)             ]-        ,testCase "getWordLengthsOrError" $-            assertEqual-                "Was the expected message produced?"-                (Left . show . vcat . map text $-                    ["Error looking up the following words in the dictionary:"-                    ,"    apple"-                    ,"    cat"-                    ]-                )-                .-                mapLeft (show . formatErrorMessage)-                $-                getWordLengthsOrError ["foo","apple","cat","bar"]-+        -- @-node:gcross.20091202203048.7022:showSumOrErrorOf_4+        -- @+node:gcross.20091203120301.1651:Multiline strings         ,testGroup "Multiline strings" $             [testCase "errorMessageText" $                 assertEqual@@ -184,5 +208,14 @@                     $                     errorMessageTextFromMultilineString "A poem:" "Roses are red.\nViolets are blue."             ]+        -- @-node:gcross.20091203120301.1651:Multiline strings+        -- @-others         ]+    -- @-node:gcross.20091202203048.6993:Documentation examples+    -- @-others+    -- @nonl+    -- @-node:gcross.20091202203048.6996:<< Tests >>+    -- @nl     ]+-- @-node:gcross.20091202203048.6990:@thin runtests.hs+-- @-leo