2013-04-09 7 views
5

Per salvare esternamente le autorizzazioni dell'account utente (ad es. In DB), voglio rappresentare un elenco di elementi di un'enumerazione che ha un'istanza derivata Enum come Int.
Ogni bit del numero è visto come un flag (o booleano) che indica se l'elemento i-esimo è presente nell'elenco.
Inserendolo in parole diverse: ogni potenza di 2 rappresenta un elemento e la somma di tali poteri è un elenco di elementi unico.Rappresenta un elenco di enum a bit come Int

Esempio:

data Permissions = IsAllowedToLogin -- 1 
       | IsModerator  -- 2 
       | IsAdmin   -- 4 
       deriving (Bounded, Enum, Eq, Show) 

enumsToInt [IsAllowedToLogin, IsAdmin] == 1 + 4 == 5 

intToEnums 3 == intToEnums (1 + 2) == [IsAllowedToLogin, IsModerator] 

La funzione di conversione di tale lista in un Int è abbastanza facile da scrivere:

enumsToInt :: (Enum a, Eq a) => [a] -> Int 
enumsToInt = foldr (\p acc -> acc + 2^fromEnum p) 0 . nub 

Si noti che la risposta accettata contiene un'implementazione molto più efficace .

Ciò che veramente mi turba è la funzione di inversione. Immagino che dovrebbe avere questo tipo:

intToEnums :: (Bounded a, Enum a) => Int -> [a] 
intToEnums = undefined    -- What I'm asking about 

Come devo affrontare questo problema?

+2

Per cominciare, hai guardato [il 'Data.Bits' modulo] (http: //hackage.haskell .org/pacchetti/archive/base/ultima/doc/html/dati-Bits.html)? –

+0

@C. A. McCann No, non ho! Pensi che sarà utile? – Jakub

+0

Non penso che abbia qualcosa che fa esattamente quello che vuoi (anche se sembra qualcosa che dovrebbe esserci) ma ha un sacco di operazioni bit a bit che renderanno le cose più facili per te. –

risposta

10

Di seguito è una soluzione completa. Dovrebbe funzionare meglio in quanto l'implementazione è basata su operazioni bit a bit piuttosto che aritmetiche, il che è un approccio molto più efficace. La soluzione fa anche del suo meglio per generalizzare le cose.

{-# LANGUAGE DefaultSignatures #-} 
import Data.Bits 
import Control.Monad 

data Permission = IsAllowedToLogin -- 1 
       | IsModerator  -- 2 
       | IsAdmin   -- 4 
       deriving (Bounded, Enum, Eq, Show) 

class ToBitMask a where 
    toBitMask :: a -> Int 
    -- | Using a DefaultSignatures extension to declare a default signature with 
    -- an `Enum` constraint without affecting the constraints of the class itself. 
    default toBitMask :: Enum a => a -> Int 
    toBitMask = shiftL 1 . fromEnum 

instance ToBitMask Permission 

instance (ToBitMask a) => ToBitMask [a] where 
    toBitMask = foldr (.|.) 0 . map toBitMask 

-- | Not making this a typeclass, since it already generalizes over all 
-- imaginable instances with help of `MonadPlus`. 
fromBitMask :: 
    (MonadPlus m, Enum a, Bounded a, ToBitMask a) => 
    Int -> m a 
fromBitMask bm = msum $ map asInBM $ enumFrom minBound where 
    asInBM a = if isInBitMask bm a then return a else mzero 

isInBitMask :: (ToBitMask a) => Int -> a -> Bool 
isInBitMask bm a = let aBM = toBitMask a in aBM == aBM .&. bm 

Esecuzione con le seguenti

main = do 
    print (fromBitMask 0 :: [Permission]) 
    print (fromBitMask 1 :: [Permission]) 
    print (fromBitMask 2 :: [Permission]) 
    print (fromBitMask 3 :: [Permission]) 
    print (fromBitMask 4 :: [Permission]) 
    print (fromBitMask 5 :: [Permission]) 
    print (fromBitMask 6 :: [Permission]) 
    print (fromBitMask 7 :: [Permission]) 

    print (fromBitMask 0 :: Maybe Permission) 
    print (fromBitMask 1 :: Maybe Permission) 
    print (fromBitMask 2 :: Maybe Permission) 
    print (fromBitMask 4 :: Maybe Permission) 

uscite

[] 
[IsAllowedToLogin] 
[IsModerator] 
[IsAllowedToLogin,IsModerator] 
[IsAdmin] 
[IsAllowedToLogin,IsAdmin] 
[IsModerator,IsAdmin] 
[IsAllowedToLogin,IsModerator,IsAdmin] 
Nothing 
Just IsAllowedToLogin 
Just IsModerator 
Just IsAdmin 
+0

Grazie mille! Sarebbe possibile aggiungere definizioni predefinite alle classi, quindi potrei semplicemente scrivere 'istanza autorizzazione ToBitMask' ??? Naturalmente assumendo che le autorizzazioni di "Permissione" siano già istanze di "Enum" e "Bounded". – Jakub

+0

@Jakub Si prega di consultare gli aggiornamenti. –

+0

@Jakub Effettua aggiornamenti per generalizzarlo ancora di più, nel caso siate interessati. –

2

EnumSet è probabilmente esattamente quello che vuoi. Ha anche una funzione intToEnums (anche se sembra funzionare solo in modo coerente con lo T Integer a dei tipi che ho provato - in particolare, T Int Char fornisce risultati imprevisti) e non ci si dovrebbe aspettare di ricreare voci duplicate dopo la serializzazione/deserializzazione (dato che è un set), mentre una lista può portare questa aspettativa.

+0

Sì, avrai bisogno di un "Int" più grande per quello. A meno che la tua macchina non usi parole a 128 bit, non dovrai nemmeno inserire i caratteri ASCII di base in quell'EnumSet. Prova 'Ordine' o qualcosa del tipo' (Bool, Bool) 'se vuoi usare' Int's. –

4

Sono sicuro che c'è qualcosa sul hackage che lo fa già, ma è abbastanza semplice da girare a mano il tuo usando the Data.Bits module.

È possibile semplificare enumsToInt al proprio simile foldl' (.|.) . map (bit . fromEnum), cioè, convertire indici interi e poi a singoli bit, quindi piegare con OR bit a bit. Se non altro, questo ti evita di preoccuparti di rimuovere i duplicati.

Per intToEnums non c'è niente di incredibilmente conveniente, ma per una soluzione rapida è possibile fare qualcosa come filter (testBit foo . fromEnum) [minBound .. maxBound]. Questo ovviamente funziona solo per i tipi Bounded e presuppone che l'enumerazione non abbia più valori di quelli esterni e che fromEnum utilizza interi consecutivi a partire da 0, ma sembra che tu stia iniziando con tutto ciò come premessa qui Comunque.

Problemi correlati