2015-05-17 13 views
6

Desidero utilizzare Kmett's lens library per accedere a un elemento di un elenco (chiave, valore) sotto una chiave specifica. In altre parole, vorrei sostituire questo codice con qualcosa di più idiomatica e, forse, più corta:Utilizzare un obiettivo per sostituire un elemento specifico di un elenco (chiave, valore)

type Headers = [ (ByteString, ByteString) ] 

headerLens :: 
    Functor f => 
    ByteString -> 
    (Maybe ByteString -> f (Maybe ByteString)) -> 
    Headers -> 
    f Headers 
headerLens header_name f headers 
    | old_header_value <- fetchHeader headers header_name = fmap 
     (\ new_header_value -> 
       replaceHeaderValue 
       headers 
       header_name 
       new_header_value 
     ) 
     (f old_header_value) 

in cui le funzioni di supporto sono definiti come di seguito:

-- | Looks for a given header and returns the value, if any 
fetchHeader :: Headers -> ByteString -> Maybe ByteString 
fetchHeader headers header_name = 
    snd <$> find (\ x -> fst x == header_name) headers 

-- | replaceHeaderValue headers header_name maybe_header_value looks for 
-- header_name. If header_name is found and maybe_header_value is nothing, it 
-- returns a new headers list with the header deleted. If header_name is found 
-- and header_value is Just new_value, it returns a new list with the header 
-- containing the new value. If header_name is not in headers and maybe_header_value 
-- is Nothing, it returns the original headers list. If header_name is not in headers 
-- and maybe_header_value is Just new_value, it returns a new list where the last element 
-- is (header_name, new_value) 
replaceHeaderValue :: Headers -> ByteString -> Maybe ByteString -> Headers 
replaceHeaderValue headers header_name maybe_header_value = 
    disect id headers 
    where 
    disect builder [] = case maybe_header_value of 
     Nothing -> headers 
     Just new_value -> builder $ (header_name, new_value):[] 
    disect builder ([email protected](hn,hv) : rest) 
     | hn /= header_name = 
      disect 
       (\ constructed_list -> builder $ el:constructed_list) 
       rest 
     | otherwise = case maybe_header_value of 
      Nothing -> builder rest 
      Just new_value -> builder $ (hn, new_value):rest  

risposta

4

Beh, se si sceglie di usare Data.Map.Map come la struttura, invece (dovrebbe essere un refactoring abbastanza facile) non sarà necessario replicare tutto questo lavoro da soli:

import qualified Data.Map as M 
import Control.Lens 

type Headers = M.Map ByteString ByteString 

fetchHeader :: Headers -> ByteString -> Maybe ByteString 
fetchHeader = flip M.lookup 

replaceHeaderValue :: Headers -> ByteString -> Maybe ByteString -> Headers 
replaceHeaderValue headers header_name maybe_header_value 
    = M.alter (const maybe_header_value) header_name headers 

allora si potrebbe mantenere il vostro headerLens così com'è. Oppure si potrebbe guardare in qualcosa di simile

headerLens name = lens (M.lookup name) (\hs mhv -> M.alter (const mhv) name hs) 

che non ha bisogno delle funzioni di supporto a tutti e in grado di avere una firma piuttosto generica per lavorare con più tipi di Map s. Esempio di utilizzo:

> let hs = M.fromList [("foo", "bar")] 
> hs ^. headerLens "foo" 
Just "bar" 
> hs ^. headerLens "baz" 
Nothing 
> headerLens "foo" .~ Just "baz" $ hs 
fromList [("foo", "baz")] 
> headerLens "foo" .~ Nothing $ hs 
fromList [] 
> headerLens "qux" .~ Just "baz" $ hs 
fromList [("foo", "bar"), ("qux", "baz")] 
> headerLens "qux" .~ Nothing $ hs 
fromList [("foo", "bar")] 

Tuttavia, questo non preserverà l'ordine degli elementi, che potrebbe essere un problema per voi. Probabilmente c'è una mappa ordinata da qualche parte, simile a quella di Python OrderedDict, ma non l'ho mai usata in Haskell prima.

+0

Grazie @bheklilr. Hai ragione sul fatto che l'ordine è importante, e in generale mi sono astenuto dall'ottimizzazione prima della profilazione. – dsign

+0

@dsign Se questo è il caso, usa semplicemente il tipo 'Headers', sostituisci' M.lookup' con 'Prelude.lookup' e reimplementa [' Data.Map.alter'] (http: //hackage.haskell. org/package/containers-0.5.6.3/docs/src/Data-Map-Base.html # alter) per gli elenchi di associazioni, che saranno un po 'complicati. Oppure puoi sfogliare Hackage per un po 'cercando di scoprire se qualcuno lo ha già fatto, perché probabilmente lo hanno fatto. – bheklilr

+0

Buon punto. Un piccolo dettaglio sgradevole che ho appena realizzato è che non posso obbedire alle leggi dell'obiettivo nella mia configurazione attuale .... quindi sì, penso di aver bisogno di passare a una mappa e ordinare i tasti con una funzione personalizzata (non è lessicografico, ma c'è un ordine * corretto *). Grazie! – dsign