2011-12-30 29 views
7

Sto estendendo una risposta a Haskell setting record field based on field name string? per aggiungere un generico getField. Sto usando gmapQi e voglio generare un errore se il tipo del sottoelemento incontrato non corrisponde al tipo atteso. Voglio che il messaggio di errore includa il nome del tipo incontrato e il nome del tipo previsto. La funzione è simile al seguente:typeOf sul tipo di ritorno

{-# LANGUAGE DeriveDataTypeable #-} 

import Data.Generics 
import Prelude hiding (catch) 
import Control.Exception 

getField :: (Data r, Typeable v) => Int -> r -> v 
getField i r = gmapQi i (e `extQ` id) r 
    where 
    e x = error $ "Type mismatch: field " ++ (show i) ++ 
        " :: " ++ (show . typeOf $ x) ++ 
        ", not " ++ (show . typeOf $ "???") 

--------------------------------------------------------------------------------- 

data Foo = Foo Int String 
    deriving(Data, Typeable) 

handleErr (ErrorCall msg) = putStrLn $ "Error -- " ++ msg 

main = do 
    let r = Foo 10 "Hello" 
    catch (print (getField 0 r :: Int)) handleErr 
    catch (print (getField 0 r :: String)) handleErr 
    catch (print (getField 1 r :: Int)) handleErr 
    catch (print (getField 1 r :: String)) handleErr 

Il problema è, non so cosa mettere al posto di "???" per ottenere il tipo di ritorno della funzione getField (vale a dire come reificare v dalla firma di tipo).

risposta

5

typeOf non valuta mai il suo argomento, quindi è possibile utilizzare qualsiasi espressione purché sia ​​del tipo corretto. In questo caso, il tipo di risultato è lo stesso del tipo restituito e, quindi è sufficiente utilizzare e x.

getField :: (Data r, Typeable v) => Int -> r -> v 
getField i r = gmapQi i (e `extQ` id) r 
    where 
    e x = error $ "Type mismatch: field " ++ (show i) ++ 
        " :: " ++ (show . typeOf $ x) ++ 
        ", not " ++ (show . typeOf $ e x) 

Questo dà i risultati attesi se eseguito:

10 
"Error -- Type mismatch: field 0 :: Int, not [Char] 
Error -- Type mismatch: field 1 :: [Char], not Int 
"Hello" 
+0

brillante! Grazie – pat

1

È possibile utilizzare (show . typeOf $ (undefined :: v)) se si aggiunge forall r v. all'inizio delle firme del tipo e si attiva l'estensione ScopedTypeVariables.

È possibile ottenere senza di esso - in genere si tratta di funzioni fittizie di aiuto per forzare i tipi nella forma desiderata - ma sarà molto brutto e non so come.