Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / BasicTypes.hs @ c92b4671

History | View | Annotate | Download (9.5 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
  , FromString(..)
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
-- | Type class for things that can be built from strings.
78
class FromString a where
79
  mkFromString :: String -> a
80

    
81
-- | Trivial 'String' instance; requires FlexibleInstances extension
82
-- though.
83
instance FromString [Char] where
84
  mkFromString = id
85

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

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

    
97
instance (FromString a, Monoid a) => MonadPlus (GenericResult a) where
98
  mzero = Bad $ mkFromString "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` mkFromString "; " `mappend` y)
102
  (Bad _) `mplus` x = x
103
  x@(Ok _) `mplus` _ = x
104

    
105
instance (FromString a) => MonadError a (GenericResult a) where
106
  throwError = Bad
107
  catchError x h = genericResult h (const x) x
108

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

    
115
instance (FromString a, Monoid a) => Alternative (GenericResult a) where
116
  empty = mzero
117
  (<|>) = mplus
118

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

    
123
-- | Eliminates a 'ResultT' value given appropriate continuations
124
elimResultT :: (Monad m)
125
            => (a -> ResultT a' m b')
126
            -> (b -> ResultT a' m b')
127
            -> ResultT a m b
128
            -> ResultT a' m b'
129
elimResultT l r = ResultT . (runResultT . result <=< runResultT)
130
  where
131
    result (Ok x)   = r x
132
    result (Bad e)  = l e
133
{-# INLINE elimResultT #-}
134

    
135
instance (Monad f) => Functor (ResultT a f) where
136
  fmap f = ResultT . liftM (fmap f) . runResultT
137

    
138
instance (Monad m, FromString a) => Applicative (ResultT a m) where
139
  pure = return
140
  (<*>) = ap
141

    
142
instance (Monad m, FromString a) => Monad (ResultT a m) where
143
  fail err = ResultT (return . Bad $ mkFromString err)
144
  return   = lift . return
145
  (>>=)    = flip (elimResultT throwError)
146

    
147
instance (Monad m, FromString a) => MonadError a (ResultT a m) where
148
  throwError = resultT . Bad
149
  catchError x h = elimResultT h return x
150

    
151
instance MonadTrans (ResultT a) where
152
  lift = ResultT . liftM Ok
153

    
154
instance (MonadIO m, FromString a) => MonadIO (ResultT a m) where
155
  liftIO = lift . liftIO
156

    
157
instance (Monad m, FromString a, Monoid a) => MonadPlus (ResultT a m) where
158
  mzero = ResultT $ return mzero
159
  -- Ensure that 'y' isn't run if 'x' contains a value. This makes it a bit
160
  -- more complicated than 'mplus' of 'GenericResult'.
161
  mplus x y = elimResultT combine return x
162
    where combine x' = ResultT $ liftM (mplus (Bad x')) (runResultT y)
163

    
164
instance (Monad m, FromString a, Monoid a) => Alternative (ResultT a m) where
165
  empty = mzero
166
  (<|>) = mplus
167

    
168
-- | Lift a `Result` value to a `ResultT`.
169
resultT :: Monad m => GenericResult a b -> ResultT a m b
170
resultT = ResultT . return
171

    
172
-- | Simple checker for whether a 'GenericResult' is OK.
173
isOk :: GenericResult a b -> Bool
174
isOk (Ok _) = True
175
isOk _      = False
176

    
177
-- | Simple checker for whether a 'GenericResult' is a failure.
178
isBad :: GenericResult a b -> Bool
179
isBad = not . isOk
180

    
181
-- | Simple filter returning only OK values of GenericResult
182
justOk :: [GenericResult a b] -> [b]
183
justOk = mapMaybe (genericResult (const Nothing) Just)
184

    
185
-- | Simple filter returning only Bad values of GenericResult
186
justBad :: [GenericResult a b] -> [a]
187
justBad = mapMaybe (genericResult Just (const Nothing))
188

    
189
-- | Converter from Either to 'GenericResult'.
190
eitherToResult :: Either a b -> GenericResult a b
191
eitherToResult (Left  s) = Bad s
192
eitherToResult (Right v) = Ok  v
193

    
194
-- | Annotate a Result with an ownership information.
195
annotateResult :: String -> Result a -> Result a
196
annotateResult owner (Bad s) = Bad $ owner ++ ": " ++ s
197
annotateResult _ v = v
198

    
199
-- | Iterate while Ok.
200
iterateOk :: (a -> GenericResult b a) -> a -> [a]
201
iterateOk f a = genericResult (const []) ((:) a . iterateOk f) (f a)
202

    
203
-- * Misc functionality
204

    
205
-- | Return the first result with a True condition, or the default otherwise.
206
select :: a            -- ^ default result
207
       -> [(Bool, a)]  -- ^ list of \"condition, result\"
208
       -> a            -- ^ first result which has a True condition, or default
209
select def = maybe def snd . find fst
210

    
211
-- * Lookup of partial names functionality
212

    
213
-- | The priority of a match in a lookup result.
214
data MatchPriority = ExactMatch
215
                   | MultipleMatch
216
                   | PartialMatch
217
                   | FailMatch
218
                   deriving (Show, Enum, Eq, Ord)
219

    
220
-- | The result of a name lookup in a list.
221
data LookupResult = LookupResult
222
  { lrMatchPriority :: MatchPriority -- ^ The result type
223
  -- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise
224
  , lrContent :: String
225
  } deriving (Show)
226

    
227
-- | Lookup results have an absolute preference ordering.
228
instance Eq LookupResult where
229
  (==) = (==) `on` lrMatchPriority
230

    
231
instance Ord LookupResult where
232
  compare = compare `on` lrMatchPriority
233

    
234
-- | Check for prefix matches in names.
235
-- Implemented in Ganeti core utils.text.MatchNameComponent
236
-- as the regexp r"^%s(\..*)?$" % re.escape(key)
237
prefixMatch :: String  -- ^ Lookup
238
            -> String  -- ^ Full name
239
            -> Bool    -- ^ Whether there is a prefix match
240
prefixMatch = isPrefixOf . (++ ".")
241

    
242
-- | Is the lookup priority a "good" one?
243
goodMatchPriority :: MatchPriority -> Bool
244
goodMatchPriority ExactMatch = True
245
goodMatchPriority PartialMatch = True
246
goodMatchPriority _ = False
247

    
248
-- | Is the lookup result an actual match?
249
goodLookupResult :: LookupResult -> Bool
250
goodLookupResult = goodMatchPriority . lrMatchPriority
251

    
252
-- | Compares a canonical name and a lookup string.
253
compareNameComponent :: String        -- ^ Canonical (target) name
254
                     -> String        -- ^ Partial (lookup) name
255
                     -> LookupResult  -- ^ Result of the lookup
256
compareNameComponent cnl lkp =
257
  select (LookupResult FailMatch lkp)
258
  [ (cnl == lkp          , LookupResult ExactMatch cnl)
259
  , (prefixMatch lkp cnl , LookupResult PartialMatch cnl)
260
  ]
261

    
262
-- | Lookup a string and choose the best result.
263
chooseLookupResult :: String       -- ^ Lookup key
264
                   -> String       -- ^ String to compare to the lookup key
265
                   -> LookupResult -- ^ Previous result
266
                   -> LookupResult -- ^ New result
267
chooseLookupResult lkp cstr old =
268
  -- default: use class order to pick the minimum result
269
  select (min new old)
270
  -- special cases:
271
  -- short circuit if the new result is an exact match
272
  [ (lrMatchPriority new == ExactMatch, new)
273
  -- if both are partial matches generate a multiple match
274
  , (partial2, LookupResult MultipleMatch lkp)
275
  ] where new = compareNameComponent cstr lkp
276
          partial2 = all ((PartialMatch==) . lrMatchPriority) [old, new]
277

    
278
-- | Find the canonical name for a lookup string in a list of names.
279
lookupName :: [String]      -- ^ List of keys
280
           -> String        -- ^ Lookup string
281
           -> LookupResult  -- ^ Result of the lookup
282
lookupName l s = foldr (chooseLookupResult s)
283
                       (LookupResult FailMatch s) l
284

    
285
-- | Wrapper for a Haskell 'Set'
286
--
287
-- This type wraps a 'Set' and it is used in the Haskell to Python
288
-- opcode generation to transform a Haskell 'Set' into a Python 'list'
289
-- without duplicate elements.
290
newtype ListSet a = ListSet { unListSet :: Set a }
291
  deriving (Eq, Show)
292

    
293
instance (Ord a, JSON a) => JSON (ListSet a) where
294
  showJSON = JSON.showJSON . unListSet
295
  readJSON = liftM ListSet . JSON.readJSON
296

    
297
emptyListSet :: ListSet a
298
emptyListSet = ListSet Set.empty