Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / BasicTypes.hs @ 31daf7db

History | View | Annotate | Download (13.8 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
  , LookupResult(..)
52
  , MatchPriority(..)
53
  , lookupName
54
  , goodLookupResult
55
  , goodMatchPriority
56
  , prefixMatch
57
  , compareNameComponent
58
  , ListSet(..)
59
  , emptyListSet
60
  ) where
61

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
307
-- * Misc functionality
308

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

    
315
-- * Lookup of partial names functionality
316

    
317
-- | The priority of a match in a lookup result.
318
data MatchPriority = ExactMatch
319
                   | MultipleMatch
320
                   | PartialMatch
321
                   | FailMatch
322
                   deriving (Show, Enum, Eq, Ord)
323

    
324
-- | The result of a name lookup in a list.
325
data LookupResult = LookupResult
326
  { lrMatchPriority :: MatchPriority -- ^ The result type
327
  -- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise
328
  , lrContent :: String
329
  } deriving (Show)
330

    
331
-- | Lookup results have an absolute preference ordering.
332
instance Eq LookupResult where
333
  (==) = (==) `on` lrMatchPriority
334

    
335
instance Ord LookupResult where
336
  compare = compare `on` lrMatchPriority
337

    
338
-- | Check for prefix matches in names.
339
-- Implemented in Ganeti core utils.text.MatchNameComponent
340
-- as the regexp r"^%s(\..*)?$" % re.escape(key)
341
prefixMatch :: String  -- ^ Lookup
342
            -> String  -- ^ Full name
343
            -> Bool    -- ^ Whether there is a prefix match
344
prefixMatch = isPrefixOf . (++ ".")
345

    
346
-- | Is the lookup priority a "good" one?
347
goodMatchPriority :: MatchPriority -> Bool
348
goodMatchPriority ExactMatch = True
349
goodMatchPriority PartialMatch = True
350
goodMatchPriority _ = False
351

    
352
-- | Is the lookup result an actual match?
353
goodLookupResult :: LookupResult -> Bool
354
goodLookupResult = goodMatchPriority . lrMatchPriority
355

    
356
-- | Compares a canonical name and a lookup string.
357
compareNameComponent :: String        -- ^ Canonical (target) name
358
                     -> String        -- ^ Partial (lookup) name
359
                     -> LookupResult  -- ^ Result of the lookup
360
compareNameComponent cnl lkp =
361
  select (LookupResult FailMatch lkp)
362
  [ (cnl == lkp          , LookupResult ExactMatch cnl)
363
  , (prefixMatch lkp cnl , LookupResult PartialMatch cnl)
364
  ]
365

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

    
382
-- | Find the canonical name for a lookup string in a list of names.
383
lookupName :: [String]      -- ^ List of keys
384
           -> String        -- ^ Lookup string
385
           -> LookupResult  -- ^ Result of the lookup
386
lookupName l s = foldr (chooseLookupResult s)
387
                       (LookupResult FailMatch s) l
388

    
389
-- | Wrapper for a Haskell 'Set'
390
--
391
-- This type wraps a 'Set' and it is used in the Haskell to Python
392
-- opcode generation to transform a Haskell 'Set' into a Python 'list'
393
-- without duplicate elements.
394
newtype ListSet a = ListSet { unListSet :: Set a }
395
  deriving (Eq, Show)
396

    
397
instance (Ord a, JSON a) => JSON (ListSet a) where
398
  showJSON = JSON.showJSON . unListSet
399
  readJSON = liftM ListSet . JSON.readJSON
400

    
401
emptyListSet :: ListSet a
402
emptyListSet = ListSet Set.empty