Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / BasicTypes.hs @ 27a5367a

History | View | Annotate | Download (11.9 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 . 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 a `ResultT`.
187
resultT :: Monad m => GenericResult a b -> ResultT a m b
188
resultT = ResultT . return
189

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

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

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

    
211
-- | Simple checker for whether a 'GenericResult' is a failure.
212
isBad :: GenericResult a b -> Bool
213
isBad = not . isOk
214

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

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

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

    
228
--- | Annotate a Result with an ownership information.
229
annotateResult :: String -> Result a -> Result a
230
annotateResult owner (Bad s) = Bad $ owner ++ ": " ++ s
231
annotateResult _ v = v
232

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

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

    
247
-- | A synonym for @flip@ 'catchErrorT'.
248
handleErrorT :: (Monad m, Error e)
249
             => (e' -> ResultT e m a) -> ResultT e' m a -> ResultT e m a
250
handleErrorT handler = elimResultT handler return
251
{-# INLINE handleErrorT #-}
252

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

    
260
-- | Iterate while Ok.
261
iterateOk :: (a -> GenericResult b a) -> a -> [a]
262
iterateOk f a = genericResult (const []) ((:) a . iterateOk f) (f a)
263

    
264
-- * Misc functionality
265

    
266
-- | Return the first result with a True condition, or the default otherwise.
267
select :: a            -- ^ default result
268
       -> [(Bool, a)]  -- ^ list of \"condition, result\"
269
       -> a            -- ^ first result which has a True condition, or default
270
select def = maybe def snd . find fst
271

    
272
-- * Lookup of partial names functionality
273

    
274
-- | The priority of a match in a lookup result.
275
data MatchPriority = ExactMatch
276
                   | MultipleMatch
277
                   | PartialMatch
278
                   | FailMatch
279
                   deriving (Show, Enum, Eq, Ord)
280

    
281
-- | The result of a name lookup in a list.
282
data LookupResult = LookupResult
283
  { lrMatchPriority :: MatchPriority -- ^ The result type
284
  -- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise
285
  , lrContent :: String
286
  } deriving (Show)
287

    
288
-- | Lookup results have an absolute preference ordering.
289
instance Eq LookupResult where
290
  (==) = (==) `on` lrMatchPriority
291

    
292
instance Ord LookupResult where
293
  compare = compare `on` lrMatchPriority
294

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

    
303
-- | Is the lookup priority a "good" one?
304
goodMatchPriority :: MatchPriority -> Bool
305
goodMatchPriority ExactMatch = True
306
goodMatchPriority PartialMatch = True
307
goodMatchPriority _ = False
308

    
309
-- | Is the lookup result an actual match?
310
goodLookupResult :: LookupResult -> Bool
311
goodLookupResult = goodMatchPriority . lrMatchPriority
312

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

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

    
339
-- | Find the canonical name for a lookup string in a list of names.
340
lookupName :: [String]      -- ^ List of keys
341
           -> String        -- ^ Lookup string
342
           -> LookupResult  -- ^ Result of the lookup
343
lookupName l s = foldr (chooseLookupResult s)
344
                       (LookupResult FailMatch s) l
345

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

    
354
instance (Ord a, JSON a) => JSON (ListSet a) where
355
  showJSON = JSON.showJSON . unListSet
356
  readJSON = liftM ListSet . JSON.readJSON
357

    
358
emptyListSet :: ListSet a
359
emptyListSet = ListSet Set.empty