Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / BasicTypes.hs @ a87a017b

History | View | Annotate | Download (9.3 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
  , resultT
30
  , Error(..) -- re-export from Control.Monad.Error
31
  , isOk
32
  , isBad
33
  , justOk
34
  , justBad
35
  , eitherToResult
36
  , annotateResult
37
  , iterateOk
38
  , select
39
  , LookupResult(..)
40
  , MatchPriority(..)
41
  , lookupName
42
  , goodLookupResult
43
  , goodMatchPriority
44
  , prefixMatch
45
  , compareNameComponent
46
  , ListSet(..)
47
  , emptyListSet
48
  ) where
49

    
50
import Control.Applicative
51
import Control.Monad
52
import Control.Monad.Error.Class
53
import Control.Monad.Trans
54
import Data.Function
55
import Data.List
56
import Data.Maybe
57
import Data.Monoid
58
import Data.Set (Set)
59
import qualified Data.Set as Set (empty)
60
import Text.JSON (JSON)
61
import qualified Text.JSON as JSON (readJSON, showJSON)
62

    
63
-- | Generic monad for our error handling mechanisms.
64
data GenericResult a b
65
  = Bad a
66
  | Ok b
67
    deriving (Show, Eq)
68

    
69
-- | Sum type structure of GenericResult.
70
genericResult :: (a -> c) -> (b -> c) -> GenericResult a b -> c
71
genericResult f _ (Bad a) = f a
72
genericResult _ g (Ok b) = g b
73

    
74
-- | Type alias for a string Result.
75
type Result = GenericResult String
76

    
77
-- | 'Monad' instance for 'GenericResult'.
78
instance (Error a) => Monad (GenericResult a) where
79
  (>>=) (Bad x) _ = Bad x
80
  (>>=) (Ok x) fn = fn x
81
  return = Ok
82
  fail   = Bad . strMsg
83

    
84
instance Functor (GenericResult a) where
85
  fmap _ (Bad msg) = Bad msg
86
  fmap fn (Ok val) = Ok (fn val)
87

    
88
instance (Error a, Monoid a) => MonadPlus (GenericResult a) where
89
  mzero = Bad $ strMsg "zero Result when used as MonadPlus"
90
  -- for mplus, when we 'add' two Bad values, we concatenate their
91
  -- error descriptions
92
  (Bad x) `mplus` (Bad y) = Bad (x `mappend` strMsg "; " `mappend` y)
93
  (Bad _) `mplus` x = x
94
  x@(Ok _) `mplus` _ = x
95

    
96
instance (Error a) => MonadError a (GenericResult a) where
97
  throwError = Bad
98
  catchError x h = genericResult h (const x) x
99

    
100
instance Applicative (GenericResult a) where
101
  pure = Ok
102
  (Bad f) <*> _       = Bad f
103
  _       <*> (Bad x) = Bad x
104
  (Ok f)  <*> (Ok x)  = Ok $ f x
105

    
106
instance (Error a, Monoid a) => Alternative (GenericResult a) where
107
  empty = mzero
108
  (<|>) = mplus
109

    
110
-- | This is a monad transformation for Result. It's implementation is
111
-- based on the implementations of MaybeT and ErrorT.
112
newtype ResultT a m b = ResultT {runResultT :: m (GenericResult a b)}
113

    
114
-- | Eliminates a 'ResultT' value given appropriate continuations
115
elimResultT :: (Monad m)
116
            => (a -> ResultT a' m b')
117
            -> (b -> ResultT a' m b')
118
            -> ResultT a m b
119
            -> ResultT a' m b'
120
elimResultT l r = ResultT . (runResultT . result <=< runResultT)
121
  where
122
    result (Ok x)   = r x
123
    result (Bad e)  = l e
124
{-# INLINE elimResultT #-}
125

    
126
instance (Monad f) => Functor (ResultT a f) where
127
  fmap f = ResultT . liftM (fmap f) . runResultT
128

    
129
instance (Monad m, Error a) => Applicative (ResultT a m) where
130
  pure = return
131
  (<*>) = ap
132

    
133
instance (Monad m, Error a) => Monad (ResultT a m) where
134
  fail err = ResultT (return . Bad $ strMsg err)
135
  return   = lift . return
136
  (>>=)    = flip (elimResultT throwError)
137

    
138
instance (Monad m, Error a) => MonadError a (ResultT a m) where
139
  throwError = resultT . Bad
140
  catchError x h = elimResultT h return x
141

    
142
instance MonadTrans (ResultT a) where
143
  lift = ResultT . liftM Ok
144

    
145
instance (MonadIO m, Error a) => MonadIO (ResultT a m) where
146
  liftIO = lift . liftIO
147

    
148
instance (Monad m, Error a, Monoid a) => MonadPlus (ResultT a m) where
149
  mzero = ResultT $ return mzero
150
  -- Ensure that 'y' isn't run if 'x' contains a value. This makes it a bit
151
  -- more complicated than 'mplus' of 'GenericResult'.
152
  mplus x y = elimResultT combine return x
153
    where combine x' = ResultT $ liftM (mplus (Bad x')) (runResultT y)
154

    
155
instance (Monad m, Error a, Monoid a) => Alternative (ResultT a m) where
156
  empty = mzero
157
  (<|>) = mplus
158

    
159
-- | Lift a `Result` value to a `ResultT`.
160
resultT :: Monad m => GenericResult a b -> ResultT a m b
161
resultT = ResultT . return
162

    
163
-- | Simple checker for whether a 'GenericResult' is OK.
164
isOk :: GenericResult a b -> Bool
165
isOk (Ok _) = True
166
isOk _      = False
167

    
168
-- | Simple checker for whether a 'GenericResult' is a failure.
169
isBad :: GenericResult a b -> Bool
170
isBad = not . isOk
171

    
172
-- | Simple filter returning only OK values of GenericResult
173
justOk :: [GenericResult a b] -> [b]
174
justOk = mapMaybe (genericResult (const Nothing) Just)
175

    
176
-- | Simple filter returning only Bad values of GenericResult
177
justBad :: [GenericResult a b] -> [a]
178
justBad = mapMaybe (genericResult Just (const Nothing))
179

    
180
-- | Converter from Either to 'GenericResult'.
181
eitherToResult :: Either a b -> GenericResult a b
182
eitherToResult (Left  s) = Bad s
183
eitherToResult (Right v) = Ok  v
184

    
185
-- | Annotate a Result with an ownership information.
186
annotateResult :: String -> Result a -> Result a
187
annotateResult owner (Bad s) = Bad $ owner ++ ": " ++ s
188
annotateResult _ v = v
189

    
190
-- | Iterate while Ok.
191
iterateOk :: (a -> GenericResult b a) -> a -> [a]
192
iterateOk f a = genericResult (const []) ((:) a . iterateOk f) (f a)
193

    
194
-- * Misc functionality
195

    
196
-- | Return the first result with a True condition, or the default otherwise.
197
select :: a            -- ^ default result
198
       -> [(Bool, a)]  -- ^ list of \"condition, result\"
199
       -> a            -- ^ first result which has a True condition, or default
200
select def = maybe def snd . find fst
201

    
202
-- * Lookup of partial names functionality
203

    
204
-- | The priority of a match in a lookup result.
205
data MatchPriority = ExactMatch
206
                   | MultipleMatch
207
                   | PartialMatch
208
                   | FailMatch
209
                   deriving (Show, Enum, Eq, Ord)
210

    
211
-- | The result of a name lookup in a list.
212
data LookupResult = LookupResult
213
  { lrMatchPriority :: MatchPriority -- ^ The result type
214
  -- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise
215
  , lrContent :: String
216
  } deriving (Show)
217

    
218
-- | Lookup results have an absolute preference ordering.
219
instance Eq LookupResult where
220
  (==) = (==) `on` lrMatchPriority
221

    
222
instance Ord LookupResult where
223
  compare = compare `on` lrMatchPriority
224

    
225
-- | Check for prefix matches in names.
226
-- Implemented in Ganeti core utils.text.MatchNameComponent
227
-- as the regexp r"^%s(\..*)?$" % re.escape(key)
228
prefixMatch :: String  -- ^ Lookup
229
            -> String  -- ^ Full name
230
            -> Bool    -- ^ Whether there is a prefix match
231
prefixMatch = isPrefixOf . (++ ".")
232

    
233
-- | Is the lookup priority a "good" one?
234
goodMatchPriority :: MatchPriority -> Bool
235
goodMatchPriority ExactMatch = True
236
goodMatchPriority PartialMatch = True
237
goodMatchPriority _ = False
238

    
239
-- | Is the lookup result an actual match?
240
goodLookupResult :: LookupResult -> Bool
241
goodLookupResult = goodMatchPriority . lrMatchPriority
242

    
243
-- | Compares a canonical name and a lookup string.
244
compareNameComponent :: String        -- ^ Canonical (target) name
245
                     -> String        -- ^ Partial (lookup) name
246
                     -> LookupResult  -- ^ Result of the lookup
247
compareNameComponent cnl lkp =
248
  select (LookupResult FailMatch lkp)
249
  [ (cnl == lkp          , LookupResult ExactMatch cnl)
250
  , (prefixMatch lkp cnl , LookupResult PartialMatch cnl)
251
  ]
252

    
253
-- | Lookup a string and choose the best result.
254
chooseLookupResult :: String       -- ^ Lookup key
255
                   -> String       -- ^ String to compare to the lookup key
256
                   -> LookupResult -- ^ Previous result
257
                   -> LookupResult -- ^ New result
258
chooseLookupResult lkp cstr old =
259
  -- default: use class order to pick the minimum result
260
  select (min new old)
261
  -- special cases:
262
  -- short circuit if the new result is an exact match
263
  [ (lrMatchPriority new == ExactMatch, new)
264
  -- if both are partial matches generate a multiple match
265
  , (partial2, LookupResult MultipleMatch lkp)
266
  ] where new = compareNameComponent cstr lkp
267
          partial2 = all ((PartialMatch==) . lrMatchPriority) [old, new]
268

    
269
-- | Find the canonical name for a lookup string in a list of names.
270
lookupName :: [String]      -- ^ List of keys
271
           -> String        -- ^ Lookup string
272
           -> LookupResult  -- ^ Result of the lookup
273
lookupName l s = foldr (chooseLookupResult s)
274
                       (LookupResult FailMatch s) l
275

    
276
-- | Wrapper for a Haskell 'Set'
277
--
278
-- This type wraps a 'Set' and it is used in the Haskell to Python
279
-- opcode generation to transform a Haskell 'Set' into a Python 'list'
280
-- without duplicate elements.
281
newtype ListSet a = ListSet { unListSet :: Set a }
282
  deriving (Eq, Show)
283

    
284
instance (Ord a, JSON a) => JSON (ListSet a) where
285
  showJSON = JSON.showJSON . unListSet
286
  readJSON = liftM ListSet . JSON.readJSON
287

    
288
emptyListSet :: ListSet a
289
emptyListSet = ListSet Set.empty