2011-12-30 71 views
7

我在擴大對Haskell setting record field based on field name string?的答案以添加通用getField。我正在使用gmapQi,如果遇到的子元素的類型與預期類型不匹配,我想要生成一個錯誤。我希望錯誤消息包含遇到的類型的名稱以及預期類型的​​名稱。功能如下:typeOf返回類型

{-# 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 

的問題是,我不知道要放什麼東西代替"???"得到getField函數的返回類型(即如何從類型簽名物化v)。

回答

5

typeOf永遠不會評估它的參數,所以只要它是正確的類型就可以使用任何表達式。在這種情況下,結果的類型與返回類型e相同,因此您可以使用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) 

這使運行時所期望的輸出:

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

輝煌!謝謝 – pat 2011-12-30 22:59:25

1

如果您將forall r v.添加到類型簽名的開頭並打開ScopedTypeVariables擴展名,則可以使用(show . typeOf $ (undefined :: v))

如果沒有這樣做,通常會有可能實現 - 通常它會涉及虛擬幫助函數,將類型強制轉換爲您想要的形式 - 但這會非常難看,我不知道如何。