Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / BasicTypes.hs @ c6d48e16

History | View | Annotate | Download (14 kB)

1
{-# LANGUAGE FlexibleInstances #-}
2
{-# LANGUAGE FlexibleContexts #-}
3
{-# LANGUAGE MultiParamTypeClasses #-}
4
{-# LANGUAGE TypeFamilies #-}
5

    
6
{-
7

    
8
Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
9

    
10
This program is free software; you can redistribute it and/or modify
11
it under the terms of the GNU General Public License as published by
12
the Free Software Foundation; either version 2 of the License, or
13
(at your option) any later version.
14

    
15
This program is distributed in the hope that it will be useful, but
16
WITHOUT ANY WARRANTY; without even the implied warranty of
17
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
General Public License for more details.
19

    
20
You should have received a copy of the GNU General Public License
21
along with this program; if not, write to the Free Software
22
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23
02110-1301, USA.
24

    
25
-}
26

    
27
module Ganeti.BasicTypes
28
  ( GenericResult(..)
29
  , genericResult
30
  , Result
31
  , ResultT(..)
32
  , mkResultT
33
  , withError
34
  , withErrorT
35
  , toError
36
  , toErrorStr
37
  , Error(..) -- re-export from Control.Monad.Error
38
  , MonadIO(..) -- re-export from Control.Monad.IO.Class
39
  , isOk
40
  , isBad
41
  , justOk
42
  , justBad
43
  , eitherToResult
44
  , annotateResult
45
  , annotateError
46
  , failError
47
  , catchErrorT
48
  , handleErrorT
49
  , iterateOk
50
  , select
51
  , runListHead
52
  , LookupResult(..)
53
  , MatchPriority(..)
54
  , lookupName
55
  , goodLookupResult
56
  , goodMatchPriority
57
  , prefixMatch
58
  , compareNameComponent
59
  , ListSet(..)
60
  , emptyListSet
61
  ) where
62

    
63
import Control.Applicative
64
import Control.Exception (try)
65
import Control.Monad
66
import Control.Monad.Base
67
import Control.Monad.Error.Class
68
import Control.Monad.Trans
69
import Control.Monad.Trans.Control
70
import Data.Function
71
import Data.List
72
import Data.Maybe
73
import Data.Monoid
74
import Data.Set (Set)
75
import qualified Data.Set as Set (empty)
76
import Text.JSON (JSON)
77
import qualified Text.JSON as JSON (readJSON, showJSON)
78

    
79
-- | Generic monad for our error handling mechanisms.
80
data GenericResult a b
81
  = Bad a
82
  | Ok b
83
    deriving (Show, Eq)
84

    
85
-- | Sum type structure of GenericResult.
86
genericResult :: (a -> c) -> (b -> c) -> GenericResult a b -> c
87
genericResult f _ (Bad a) = f a
88
genericResult _ g (Ok b) = g b
89
{-# INLINE genericResult #-}
90

    
91
-- | Type alias for a string Result.
92
type Result = GenericResult String
93

    
94
-- | 'Monad' instance for 'GenericResult'.
95
instance (Error a) => Monad (GenericResult a) where
96
  (>>=) (Bad x) _ = Bad x
97
  (>>=) (Ok x) fn = fn x
98
  return = Ok
99
  fail   = Bad . strMsg
100

    
101
instance Functor (GenericResult a) where
102
  fmap _ (Bad msg) = Bad msg
103
  fmap fn (Ok val) = Ok (fn val)
104

    
105
instance (Error a, Monoid a) => MonadPlus (GenericResult a) where
106
  mzero = Bad $ strMsg "zero Result when used as MonadPlus"
107
  -- for mplus, when we 'add' two Bad values, we concatenate their
108
  -- error descriptions
109
  (Bad x) `mplus` (Bad y) = Bad (x `mappend` strMsg "; " `mappend` y)
110
  (Bad _) `mplus` x = x
111
  x@(Ok _) `mplus` _ = x
112

    
113
instance (Error a) => MonadError a (GenericResult a) where
114
  throwError = Bad
115
  {-# INLINE throwError #-}
116
  catchError x h = genericResult h (const x) x
117
  {-# INLINE catchError #-}
118

    
119
instance Applicative (GenericResult a) where
120
  pure = Ok
121
  (Bad f) <*> _       = Bad f
122
  _       <*> (Bad x) = Bad x
123
  (Ok f)  <*> (Ok x)  = Ok $ f x
124

    
125
instance (Error a, Monoid a) => Alternative (GenericResult a) where
126
  empty = mzero
127
  (<|>) = mplus
128

    
129
-- | This is a monad transformation for Result. It's implementation is
130
-- based on the implementations of MaybeT and ErrorT.
131
--
132
-- 'ResultT' is very similar to @ErrorT@, but with one subtle difference:
133
-- If 'mplus' combines two failing operations, errors of both of them
134
-- are combined.
135
newtype ResultT a m b = ResultT {runResultT :: m (GenericResult a b)}
136

    
137
-- | Eliminates a 'ResultT' value given appropriate continuations
138
elimResultT :: (Monad m)
139
            => (a -> ResultT a' m b')
140
            -> (b -> ResultT a' m b')
141
            -> ResultT a m b
142
            -> ResultT a' m b'
143
elimResultT l r = ResultT . (runResultT . result <=< runResultT)
144
  where
145
    result (Ok x)   = r x
146
    result (Bad e)  = l e
147
{-# INLINE elimResultT #-}
148

    
149
instance (Monad f) => Functor (ResultT a f) where
150
  fmap f = ResultT . liftM (fmap f) . runResultT
151

    
152
instance (Monad m, Error a) => Applicative (ResultT a m) where
153
  pure = return
154
  (<*>) = ap
155

    
156
instance (Monad m, Error a) => Monad (ResultT a m) where
157
  fail err = ResultT (return . Bad $ strMsg err)
158
  return   = lift . return
159
  (>>=)    = flip (elimResultT throwError)
160

    
161
instance (Monad m, Error a) => MonadError a (ResultT a m) where
162
  throwError = ResultT . return . Bad
163
  catchError = catchErrorT
164

    
165
instance MonadTrans (ResultT a) where
166
  lift = ResultT . liftM Ok
167

    
168
-- | The instance catches any 'IOError' using 'try' and converts it into an
169
-- error message using 'strMsg'.
170
--
171
-- This way, monadic code within 'ResultT' that uses solely 'liftIO' to
172
-- include 'IO' actions ensures that all IO exceptions are handled.
173
--
174
-- Other exceptions (see instances of 'Exception') are not currently handled.
175
-- This might be revised in the future.
176
instance (MonadIO m, Error a) => MonadIO (ResultT a m) where
177
  liftIO = ResultT . liftIO
178
                   . liftM (either (failError . show) return)
179
                   . (try :: IO a -> IO (Either IOError a))
180

    
181
instance (MonadBase IO m, Error a) => MonadBase IO (ResultT a m) where
182
  liftBase = ResultT . liftBase
183
                   . liftM (either (failError . show) return)
184
                   . (try :: IO a -> IO (Either IOError a))
185

    
186
instance (Error a) => MonadTransControl (ResultT a) where
187
  newtype StT (ResultT a) b = StResultT { runStResultT :: GenericResult a b }
188
  liftWith f = ResultT . liftM return $ f (liftM StResultT . runResultT)
189
  restoreT = ResultT . liftM runStResultT
190
  {-# INLINE liftWith #-}
191
  {-# INLINE restoreT #-}
192

    
193
instance (Error a, MonadBaseControl IO m)
194
         => MonadBaseControl IO (ResultT a m) where
195
  newtype StM (ResultT a m) b
196
    = StMResultT { runStMResultT :: ComposeSt (ResultT a) m b }
197
  liftBaseWith = defaultLiftBaseWith StMResultT
198
  restoreM = defaultRestoreM runStMResultT
199
  {-# INLINE liftBaseWith #-}
200
  {-# INLINE restoreM #-}
201

    
202
instance (Monad m, Error a, Monoid a) => MonadPlus (ResultT a m) where
203
  mzero = ResultT $ return mzero
204
  -- Ensure that 'y' isn't run if 'x' contains a value. This makes it a bit
205
  -- more complicated than 'mplus' of 'GenericResult'.
206
  mplus x y = elimResultT combine return x
207
    where combine x' = ResultT $ liftM (mplus (Bad x')) (runResultT y)
208

    
209
instance (Monad m, Error a, Monoid a) => Alternative (ResultT a m) where
210
  empty = mzero
211
  (<|>) = mplus
212

    
213
-- | Changes the error message of a result value, if present.
214
-- Note that since 'GenericResult' is also a 'MonadError', this function
215
-- is a generalization of
216
-- @(Error e') => (e' -> e) -> GenericResult e' a -> GenericResult e a@
217
withError :: (MonadError e m) => (e' -> e) -> GenericResult e' a -> m a
218
withError f = genericResult (throwError . f) return
219

    
220
-- | Changes the error message of a @ResultT@ value, if present.
221
withErrorT :: (Monad m, Error e)
222
           => (e' -> e) -> ResultT e' m a -> ResultT e m a
223
withErrorT f = ResultT . liftM (withError f) . runResultT
224

    
225
-- | Lift a 'Result' value to any 'MonadError'. Since 'ResultT' is itself its
226
-- instance, it's a generalization of
227
-- @Monad m => GenericResult a b -> ResultT a m b@.
228
toError :: (MonadError e m) => GenericResult e a -> m a
229
toError = genericResult throwError return
230
{-# INLINE toError #-}
231

    
232
-- | An alias for @withError strMsg@, which is often used to lift a pure error
233
-- to a monad stack. See also 'annotateResult'.
234
toErrorStr :: (MonadError e m, Error e) => Result a -> m a
235
toErrorStr = withError strMsg
236

    
237
-- | Converts a monadic result with a 'String' message into
238
-- a 'ResultT' with an arbitrary 'Error'.
239
--
240
-- Expects that the given action has already taken care of any possible
241
-- errors. In particular, if applied on @IO (Result a)@, any exceptions
242
-- should be handled by the given action.
243
--
244
-- See also 'toErrorStr'.
245
mkResultT :: (Monad m, Error e) => m (Result a) -> ResultT e m a
246
mkResultT = ResultT . liftM toErrorStr
247

    
248
-- | Simple checker for whether a 'GenericResult' is OK.
249
isOk :: GenericResult a b -> Bool
250
isOk (Ok _) = True
251
isOk _      = False
252

    
253
-- | Simple checker for whether a 'GenericResult' is a failure.
254
isBad :: GenericResult a b -> Bool
255
isBad = not . isOk
256

    
257
-- | Simple filter returning only OK values of GenericResult
258
justOk :: [GenericResult a b] -> [b]
259
justOk = mapMaybe (genericResult (const Nothing) Just)
260

    
261
-- | Simple filter returning only Bad values of GenericResult
262
justBad :: [GenericResult a b] -> [a]
263
justBad = mapMaybe (genericResult Just (const Nothing))
264

    
265
-- | Converter from Either to 'GenericResult'.
266
eitherToResult :: Either a b -> GenericResult a b
267
eitherToResult (Left  s) = Bad s
268
eitherToResult (Right v) = Ok  v
269

    
270
-- | Annotate an error with an ownership information, lifting it to a
271
-- 'MonadError'. Since 'Result' is an instance of 'MonadError' itself,
272
-- it's a generalization of type @String -> Result a -> Result a@.
273
-- See also 'toErrorStr'.
274
annotateResult :: (MonadError e m, Error e) => String -> Result a -> m a
275
annotateResult owner = toErrorStr . annotateError owner
276

    
277
-- | Annotate an error with an ownership information inside a 'MonadError'.
278
-- See also 'annotateResult'.
279
annotateError :: (MonadError e m, Error e, Monoid e) => String -> m a -> m a
280
annotateError owner =
281
  flip catchError (throwError . mappend (strMsg $ owner ++ ": "))
282
{-# INLINE annotateError #-}
283

    
284
-- | Throws a 'String' message as an error in a 'MonadError'.
285
-- This is a generalization of 'Bad'.
286
-- It's similar to 'fail', but works within a 'MonadError', avoiding the
287
-- unsafe nature of 'fail'.
288
failError :: (MonadError e m, Error e) => String -> m a
289
failError = throwError . strMsg
290

    
291
-- | A synonym for @flip@ 'catchErrorT'.
292
handleErrorT :: (Monad m, Error e)
293
             => (e' -> ResultT e m a) -> ResultT e' m a -> ResultT e m a
294
handleErrorT handler = elimResultT handler return
295
{-# INLINE handleErrorT #-}
296

    
297
-- | Catches an error in a @ResultT@ value. This is similar to 'catchError',
298
-- but in addition allows to change the error type.
299
catchErrorT :: (Monad m, Error e)
300
            => ResultT e' m a -> (e' -> ResultT e m a) -> ResultT e m a
301
catchErrorT = flip handleErrorT
302
{-# INLINE catchErrorT #-}
303

    
304
-- | Iterate while Ok.
305
iterateOk :: (a -> GenericResult b a) -> a -> [a]
306
iterateOk f a = genericResult (const []) ((:) a . iterateOk f) (f a)
307

    
308
-- * Misc functionality
309

    
310
-- | Return the first result with a True condition, or the default otherwise.
311
select :: a            -- ^ default result
312
       -> [(Bool, a)]  -- ^ list of \"condition, result\"
313
       -> a            -- ^ first result which has a True condition, or default
314
select def = maybe def snd . find fst
315

    
316
-- | Apply a function to the first element of a list, return the default
317
-- value, if the list is empty. This is just a convenient combination of
318
-- maybe and listToMaybe.
319
runListHead :: a -> (b -> a) -> [b] -> a
320
runListHead a f = maybe a f . listToMaybe
321

    
322
-- * Lookup of partial names functionality
323

    
324
-- | The priority of a match in a lookup result.
325
data MatchPriority = ExactMatch
326
                   | MultipleMatch
327
                   | PartialMatch
328
                   | FailMatch
329
                   deriving (Show, Enum, Eq, Ord)
330

    
331
-- | The result of a name lookup in a list.
332
data LookupResult = LookupResult
333
  { lrMatchPriority :: MatchPriority -- ^ The result type
334
  -- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise
335
  , lrContent :: String
336
  } deriving (Show)
337

    
338
-- | Lookup results have an absolute preference ordering.
339
instance Eq LookupResult where
340
  (==) = (==) `on` lrMatchPriority
341

    
342
instance Ord LookupResult where
343
  compare = compare `on` lrMatchPriority
344

    
345
-- | Check for prefix matches in names.
346
-- Implemented in Ganeti core utils.text.MatchNameComponent
347
-- as the regexp r"^%s(\..*)?$" % re.escape(key)
348
prefixMatch :: String  -- ^ Lookup
349
            -> String  -- ^ Full name
350
            -> Bool    -- ^ Whether there is a prefix match
351
prefixMatch = isPrefixOf . (++ ".")
352

    
353
-- | Is the lookup priority a "good" one?
354
goodMatchPriority :: MatchPriority -> Bool
355
goodMatchPriority ExactMatch = True
356
goodMatchPriority PartialMatch = True
357
goodMatchPriority _ = False
358

    
359
-- | Is the lookup result an actual match?
360
goodLookupResult :: LookupResult -> Bool
361
goodLookupResult = goodMatchPriority . lrMatchPriority
362

    
363
-- | Compares a canonical name and a lookup string.
364
compareNameComponent :: String        -- ^ Canonical (target) name
365
                     -> String        -- ^ Partial (lookup) name
366
                     -> LookupResult  -- ^ Result of the lookup
367
compareNameComponent cnl lkp =
368
  select (LookupResult FailMatch lkp)
369
  [ (cnl == lkp          , LookupResult ExactMatch cnl)
370
  , (prefixMatch lkp cnl , LookupResult PartialMatch cnl)
371
  ]
372

    
373
-- | Lookup a string and choose the best result.
374
chooseLookupResult :: String       -- ^ Lookup key
375
                   -> String       -- ^ String to compare to the lookup key
376
                   -> LookupResult -- ^ Previous result
377
                   -> LookupResult -- ^ New result
378
chooseLookupResult lkp cstr old =
379
  -- default: use class order to pick the minimum result
380
  select (min new old)
381
  -- special cases:
382
  -- short circuit if the new result is an exact match
383
  [ (lrMatchPriority new == ExactMatch, new)
384
  -- if both are partial matches generate a multiple match
385
  , (partial2, LookupResult MultipleMatch lkp)
386
  ] where new = compareNameComponent cstr lkp
387
          partial2 = all ((PartialMatch==) . lrMatchPriority) [old, new]
388

    
389
-- | Find the canonical name for a lookup string in a list of names.
390
lookupName :: [String]      -- ^ List of keys
391
           -> String        -- ^ Lookup string
392
           -> LookupResult  -- ^ Result of the lookup
393
lookupName l s = foldr (chooseLookupResult s)
394
                       (LookupResult FailMatch s) l
395

    
396
-- | Wrapper for a Haskell 'Set'
397
--
398
-- This type wraps a 'Set' and it is used in the Haskell to Python
399
-- opcode generation to transform a Haskell 'Set' into a Python 'list'
400
-- without duplicate elements.
401
newtype ListSet a = ListSet { unListSet :: Set a }
402
  deriving (Eq, Show)
403

    
404
instance (Ord a, JSON a) => JSON (ListSet a) where
405
  showJSON = JSON.showJSON . unListSet
406
  readJSON = liftM ListSet . JSON.readJSON
407

    
408
emptyListSet :: ListSet a
409
emptyListSet = ListSet Set.empty