Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / BasicTypes.hs @ 87f15934

History | View | Annotate | Download (12.2 kB)

1
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
2

    
3
{-
4

    
5
Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
6

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

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

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

    
22
-}
23

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

    
58
import Control.Applicative
59
import Control.Monad
60
import Control.Monad.Error.Class
61
import Control.Monad.Trans
62
import Data.Function
63
import Data.List
64
import Data.Maybe
65
import Data.Monoid
66
import Data.Set (Set)
67
import qualified Data.Set as Set (empty)
68
import Text.JSON (JSON)
69
import qualified Text.JSON as JSON (readJSON, showJSON)
70

    
71
-- | Generic monad for our error handling mechanisms.
72
data GenericResult a b
73
  = Bad a
74
  | Ok b
75
    deriving (Show, Eq)
76

    
77
-- | Sum type structure of GenericResult.
78
genericResult :: (a -> c) -> (b -> c) -> GenericResult a b -> c
79
genericResult f _ (Bad a) = f a
80
genericResult _ g (Ok b) = g b
81
{-# INLINE genericResult #-}
82

    
83
-- | Type alias for a string Result.
84
type Result = GenericResult String
85

    
86
-- | 'Monad' instance for 'GenericResult'.
87
instance (Error a) => Monad (GenericResult a) where
88
  (>>=) (Bad x) _ = Bad x
89
  (>>=) (Ok x) fn = fn x
90
  return = Ok
91
  fail   = Bad . strMsg
92

    
93
instance Functor (GenericResult a) where
94
  fmap _ (Bad msg) = Bad msg
95
  fmap fn (Ok val) = Ok (fn val)
96

    
97
instance (Error a, Monoid a) => MonadPlus (GenericResult a) where
98
  mzero = Bad $ strMsg "zero Result when used as MonadPlus"
99
  -- for mplus, when we 'add' two Bad values, we concatenate their
100
  -- error descriptions
101
  (Bad x) `mplus` (Bad y) = Bad (x `mappend` strMsg "; " `mappend` y)
102
  (Bad _) `mplus` x = x
103
  x@(Ok _) `mplus` _ = x
104

    
105
instance (Error a) => MonadError a (GenericResult a) where
106
  throwError = Bad
107
  {-# INLINE throwError #-}
108
  catchError x h = genericResult h (const x) x
109
  {-# INLINE catchError #-}
110

    
111
instance Applicative (GenericResult a) where
112
  pure = Ok
113
  (Bad f) <*> _       = Bad f
114
  _       <*> (Bad x) = Bad x
115
  (Ok f)  <*> (Ok x)  = Ok $ f x
116

    
117
instance (Error a, Monoid a) => Alternative (GenericResult a) where
118
  empty = mzero
119
  (<|>) = mplus
120

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

    
129
-- | Eliminates a 'ResultT' value given appropriate continuations
130
elimResultT :: (Monad m)
131
            => (a -> ResultT a' m b')
132
            -> (b -> ResultT a' m b')
133
            -> ResultT a m b
134
            -> ResultT a' m b'
135
elimResultT l r = ResultT . (runResultT . result <=< runResultT)
136
  where
137
    result (Ok x)   = r x
138
    result (Bad e)  = l e
139
{-# INLINE elimResultT #-}
140

    
141
instance (Monad f) => Functor (ResultT a f) where
142
  fmap f = ResultT . liftM (fmap f) . runResultT
143

    
144
instance (Monad m, Error a) => Applicative (ResultT a m) where
145
  pure = return
146
  (<*>) = ap
147

    
148
instance (Monad m, Error a) => Monad (ResultT a m) where
149
  fail err = ResultT (return . Bad $ strMsg err)
150
  return   = lift . return
151
  (>>=)    = flip (elimResultT throwError)
152

    
153
instance (Monad m, Error a) => MonadError a (ResultT a m) where
154
  throwError = ResultT . return . Bad
155
  catchError = catchErrorT
156

    
157
instance MonadTrans (ResultT a) where
158
  lift = ResultT . liftM Ok
159

    
160
instance (MonadIO m, Error a) => MonadIO (ResultT a m) where
161
  liftIO = lift . liftIO
162

    
163
instance (Monad m, Error a, Monoid a) => MonadPlus (ResultT a m) where
164
  mzero = ResultT $ return mzero
165
  -- Ensure that 'y' isn't run if 'x' contains a value. This makes it a bit
166
  -- more complicated than 'mplus' of 'GenericResult'.
167
  mplus x y = elimResultT combine return x
168
    where combine x' = ResultT $ liftM (mplus (Bad x')) (runResultT y)
169

    
170
instance (Monad m, Error a, Monoid a) => Alternative (ResultT a m) where
171
  empty = mzero
172
  (<|>) = mplus
173

    
174
-- | Changes the error message of a result value, if present.
175
-- Note that since 'GenericResult' is also a 'MonadError', this function
176
-- is a generalization of
177
-- @(Error e') => (e' -> e) -> GenericResult e' a -> GenericResult e a@
178
withError :: (MonadError e m) => (e' -> e) -> GenericResult e' a -> m a
179
withError f = genericResult (throwError . f) return
180

    
181
-- | Changes the error message of a @ResultT@ value, if present.
182
withErrorT :: (Monad m, Error e)
183
           => (e' -> e) -> ResultT e' m a -> ResultT e m a
184
withErrorT f = ResultT . liftM (withError f) . runResultT
185

    
186
-- | Lift a 'Result' value to any 'MonadError'. Since 'ResultT' is itself its
187
-- instance, it's a generalization of
188
-- @Monad m => GenericResult a b -> ResultT a m b@.
189
resultT :: (MonadError e m) => GenericResult e a -> m a
190
resultT = genericResult throwError return
191
{-# INLINE resultT #-}
192

    
193
-- | An alias for @withError strMsg@, which is often used to lift a pure error
194
-- to a monad stack. See also 'annotateResult'.
195
toErrorStr :: (MonadError e m, Error e) => Result a -> m a
196
toErrorStr = withError strMsg
197

    
198
-- | Converts a monadic result with a 'String' message into
199
-- a 'ResultT' with an arbitrary 'Error'.
200
--
201
-- Expects that the given action has already taken care of any possible
202
-- errors. In particular, if applied on @IO (Result a)@, any exceptions
203
-- should be handled by the given action.
204
--
205
-- See also 'toErrorStr'.
206
mkResultT :: (Monad m, Error e) => m (Result a) -> ResultT e m a
207
mkResultT = ResultT . liftM toErrorStr
208

    
209
-- | Simple checker for whether a 'GenericResult' is OK.
210
isOk :: GenericResult a b -> Bool
211
isOk (Ok _) = True
212
isOk _      = False
213

    
214
-- | Simple checker for whether a 'GenericResult' is a failure.
215
isBad :: GenericResult a b -> Bool
216
isBad = not . isOk
217

    
218
-- | Simple filter returning only OK values of GenericResult
219
justOk :: [GenericResult a b] -> [b]
220
justOk = mapMaybe (genericResult (const Nothing) Just)
221

    
222
-- | Simple filter returning only Bad values of GenericResult
223
justBad :: [GenericResult a b] -> [a]
224
justBad = mapMaybe (genericResult Just (const Nothing))
225

    
226
-- | Converter from Either to 'GenericResult'.
227
eitherToResult :: Either a b -> GenericResult a b
228
eitherToResult (Left  s) = Bad s
229
eitherToResult (Right v) = Ok  v
230

    
231
-- | Annotate an error with an ownership information, lifting it to a
232
-- 'MonadError'. Since 'Result' is an instance of 'MonadError' itself,
233
-- it's a generalization of type @String -> Result a -> Result a@.
234
-- See also 'toErrorStr'.
235
annotateResult :: (MonadError e m, Error e) => String -> Result a -> m a
236
annotateResult owner = toErrorStr . annotateError owner
237

    
238
-- | Annotate an error with an ownership information inside a 'MonadError'.
239
-- See also 'annotateResult'.
240
annotateError :: (MonadError e m, Error e, Monoid e) => String -> m a -> m a
241
annotateError owner =
242
  flip catchError (throwError . mappend (strMsg $ owner ++ ": "))
243
{-# INLINE annotateError #-}
244

    
245
-- | Throws a 'String' message as an error in a 'MonadError'.
246
-- This is a generalization of 'Bad'.
247
-- It's similar to 'fail', but works within a 'MonadError', avoiding the
248
-- unsafe nature of 'fail'.
249
failError :: (MonadError e m, Error e) => String -> m a
250
failError = throwError . strMsg
251

    
252
-- | A synonym for @flip@ 'catchErrorT'.
253
handleErrorT :: (Monad m, Error e)
254
             => (e' -> ResultT e m a) -> ResultT e' m a -> ResultT e m a
255
handleErrorT handler = elimResultT handler return
256
{-# INLINE handleErrorT #-}
257

    
258
-- | Catches an error in a @ResultT@ value. This is similar to 'catchError',
259
-- but in addition allows to change the error type.
260
catchErrorT :: (Monad m, Error e)
261
            => ResultT e' m a -> (e' -> ResultT e m a) -> ResultT e m a
262
catchErrorT = flip handleErrorT
263
{-# INLINE catchErrorT #-}
264

    
265
-- | Iterate while Ok.
266
iterateOk :: (a -> GenericResult b a) -> a -> [a]
267
iterateOk f a = genericResult (const []) ((:) a . iterateOk f) (f a)
268

    
269
-- * Misc functionality
270

    
271
-- | Return the first result with a True condition, or the default otherwise.
272
select :: a            -- ^ default result
273
       -> [(Bool, a)]  -- ^ list of \"condition, result\"
274
       -> a            -- ^ first result which has a True condition, or default
275
select def = maybe def snd . find fst
276

    
277
-- * Lookup of partial names functionality
278

    
279
-- | The priority of a match in a lookup result.
280
data MatchPriority = ExactMatch
281
                   | MultipleMatch
282
                   | PartialMatch
283
                   | FailMatch
284
                   deriving (Show, Enum, Eq, Ord)
285

    
286
-- | The result of a name lookup in a list.
287
data LookupResult = LookupResult
288
  { lrMatchPriority :: MatchPriority -- ^ The result type
289
  -- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise
290
  , lrContent :: String
291
  } deriving (Show)
292

    
293
-- | Lookup results have an absolute preference ordering.
294
instance Eq LookupResult where
295
  (==) = (==) `on` lrMatchPriority
296

    
297
instance Ord LookupResult where
298
  compare = compare `on` lrMatchPriority
299

    
300
-- | Check for prefix matches in names.
301
-- Implemented in Ganeti core utils.text.MatchNameComponent
302
-- as the regexp r"^%s(\..*)?$" % re.escape(key)
303
prefixMatch :: String  -- ^ Lookup
304
            -> String  -- ^ Full name
305
            -> Bool    -- ^ Whether there is a prefix match
306
prefixMatch = isPrefixOf . (++ ".")
307

    
308
-- | Is the lookup priority a "good" one?
309
goodMatchPriority :: MatchPriority -> Bool
310
goodMatchPriority ExactMatch = True
311
goodMatchPriority PartialMatch = True
312
goodMatchPriority _ = False
313

    
314
-- | Is the lookup result an actual match?
315
goodLookupResult :: LookupResult -> Bool
316
goodLookupResult = goodMatchPriority . lrMatchPriority
317

    
318
-- | Compares a canonical name and a lookup string.
319
compareNameComponent :: String        -- ^ Canonical (target) name
320
                     -> String        -- ^ Partial (lookup) name
321
                     -> LookupResult  -- ^ Result of the lookup
322
compareNameComponent cnl lkp =
323
  select (LookupResult FailMatch lkp)
324
  [ (cnl == lkp          , LookupResult ExactMatch cnl)
325
  , (prefixMatch lkp cnl , LookupResult PartialMatch cnl)
326
  ]
327

    
328
-- | Lookup a string and choose the best result.
329
chooseLookupResult :: String       -- ^ Lookup key
330
                   -> String       -- ^ String to compare to the lookup key
331
                   -> LookupResult -- ^ Previous result
332
                   -> LookupResult -- ^ New result
333
chooseLookupResult lkp cstr old =
334
  -- default: use class order to pick the minimum result
335
  select (min new old)
336
  -- special cases:
337
  -- short circuit if the new result is an exact match
338
  [ (lrMatchPriority new == ExactMatch, new)
339
  -- if both are partial matches generate a multiple match
340
  , (partial2, LookupResult MultipleMatch lkp)
341
  ] where new = compareNameComponent cstr lkp
342
          partial2 = all ((PartialMatch==) . lrMatchPriority) [old, new]
343

    
344
-- | Find the canonical name for a lookup string in a list of names.
345
lookupName :: [String]      -- ^ List of keys
346
           -> String        -- ^ Lookup string
347
           -> LookupResult  -- ^ Result of the lookup
348
lookupName l s = foldr (chooseLookupResult s)
349
                       (LookupResult FailMatch s) l
350

    
351
-- | Wrapper for a Haskell 'Set'
352
--
353
-- This type wraps a 'Set' and it is used in the Haskell to Python
354
-- opcode generation to transform a Haskell 'Set' into a Python 'list'
355
-- without duplicate elements.
356
newtype ListSet a = ListSet { unListSet :: Set a }
357
  deriving (Eq, Show)
358

    
359
instance (Ord a, JSON a) => JSON (ListSet a) where
360
  showJSON = JSON.showJSON . unListSet
361
  readJSON = liftM ListSet . JSON.readJSON
362

    
363
emptyListSet :: ListSet a
364
emptyListSet = ListSet Set.empty