Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / BasicTypes.hs @ d5868411

History | View | Annotate | Download (13.7 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
  , isOk
39
  , isBad
40
  , justOk
41
  , justBad
42
  , eitherToResult
43
  , annotateResult
44
  , annotateError
45
  , failError
46
  , catchErrorT
47
  , handleErrorT
48
  , iterateOk
49
  , select
50
  , LookupResult(..)
51
  , MatchPriority(..)
52
  , lookupName
53
  , goodLookupResult
54
  , goodMatchPriority
55
  , prefixMatch
56
  , compareNameComponent
57
  , ListSet(..)
58
  , emptyListSet
59
  ) where
60

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
306
-- * Misc functionality
307

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

    
314
-- * Lookup of partial names functionality
315

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

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

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

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

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

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

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

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

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

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

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

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

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