2017-12-27 460 views
2

我有一個系統,其中包含很多不同的功能。我希望用戶 能夠將數據從shell傳遞到這些函數中。如果它們傳遞的數據類型錯誤,則在執行該功能時應顯示錯誤。Haskell:函數在包裝數據上的應用

數據需要以一般方式存儲爲相同類型,以便在傳遞給exec函數之前可以將其存儲在列表中。

data Data = DInt Int | DBool Bool | DChar Char ..... 

有沒有一種方法可以將數據列表傳遞到像這樣的函數中?

exec :: [Data] -> (wrapped up function) -> Either Error Data 

如果函數期待一個布爾而是一個Int發現,則會引發錯誤等

功能必須包裹在某種結構允許該應用程序,但我不確定是否有簡單的方法來實現這種行爲。

謝謝,第二次試圖寫這個,所以請要求澄清。

+0

您是否熟悉'Data.Dynamic'? –

回答

2

readMaybe位於Text.Read包中。我會嘗試讀取輸入,如果返回Nothing嘗試解析另一種類型。你必須保持命令這樣做。例如,第一Int,然後Bool

http://hackage.haskell.org/package/base-4.10.1.0/docs/Text-Read.html#v:readMaybe

+1

該提議使用'String'作爲窮人的'Data.Dynamic.Dynamic'並解析('readMaybe')來代替顯式的'TypeRep'。如果數據是以字符串形式開始的,那麼這是一個很好的策略,但如果數據實際上是一個Haskell值,那麼調用'show'只是爲了統一類型,這有點笨拙。 –

4

認爲你所要求的是完全不地道。我將提出一個你永遠不應該使用的答案,因爲如果它是你想要的,那麼你正在以錯誤的方式解決問題。

一個壞的,但有趣的解決方案

概述:我們將構建盒 - 的任何類型的值。這些框將攜帶我們可用於相等性檢查的值和類型表示,以確保我們的函數應用程序和返回類型都是正確的。然後,我們在應用函數之前手動檢查類型表示(表示類型的值,這些值在編譯時丟失)。記住函數和參數類型是不透明的 - 它們在編譯時被擦除 - 所以我們需要使用有罪功能unsafeCoerce

所以先從我們需要生存類型,分型和不安全的要挾:

{-# LANGUAGE ExistentialQuantification #-} 
{-# LANGUAGE TypeApplications #-} 
import Data.Typeable 
import Unsafe.Coerce 

這個盒子是我們的生存:

data Box = forall a. Box (TypeRep, a) 

如果我們做一個模塊提供一個安全的API,我們」 d想做一個聰明的構造函數:

-- | Convert a type into a "box" of the value and the value's type. 
mkBox :: Typeable a => a -> Box 
mkBox a = Box (typeOf a, a) 

您的exec函數現在不需要獲取這個醜陋的總和類型的列表(Data),而是可以以框的形式取出框和函數的列表,然後將每個參數應用於函數,以獲得結果。注意,調用者需要靜態地知道返回類型 - 由Proxy參數表示 - 否則我們必須返回一個Box,因爲結果很無用。

exec :: Typeable a 
    => [Box] --^Arguments 
    -> Box --^Function 
    -> Proxy a 
    -> Either String a 
exec [] (Box (fTy,f)) p 
    | fTy == typeRep p = Right $ unsafeCoerce f 
    -- ^^ The function is fully applied. If it is the type expected 
    -- by the caller then we can return that value. 
    | otherwise  = Left "Final value does not match proxy type." 
exec ((Box (aTy,a)):as) (Box (fTy,f)) p 
    | Just appliedTy <- funResultTy fTy aTy = exec as (Box (appliedTy, (unsafeCoerce f) (unsafeCoerce a))) p 
    -- ^^ There is at least one more argument 
    | otherwise = Left "Some argument was the wrong type. XXX we can thread the arg number through if desired" 
    -- ^^ The function expected a different argument type _or_ it was fully applied (too many argument supplied!) 

我們可以測試三種結果只是:

main :: IO() 
main = 
    do print $ exec [mkBox (1::Int), mkBox (2::Int)] (mkBox ((+) :: Int -> Int -> Int)) (Proxy @Int) 
    print $ exec [mkBox (1::Int)] (mkBox (last :: [Int] -> Int)) (Proxy @Int) 
    print $ exec [mkBox (1::Int)] (mkBox (id :: Int -> Int)) (Proxy @Double) 

產量:

Right 3 
Left "Some argument was the wrong type. XXX we can thread the arg number through if desired" 
Left "Final value does not match proxy type." 

編輯:我應該指出,Box這個API是更多的教育,比必要的,因爲不夠簡明您可以使用Data.Dynamic。例如(因爲代理可以推斷,我也更改了API):

{-# LANGUAGE ExistentialQuantification #-} 
{-# LANGUAGE GADTs #-} 
import Data.Dynamic 
import Type.Reflection 

type Box = Dynamic 

-- | Convert a type into a "box" of the value and the 
-- value's type. 
mkBox :: Typeable a => a -> Box 
mkBox = toDyn 

exec :: Typeable a 
    => [Box] --^Arguments 
    -> Box --^Function 
    -> Either String a 
exec [] f = case fromDynamic f of 
       Just x -> Right x 
       Nothing -> Left "Final type did not match proxy" 
exec (a:as) f 
    | Just applied <- dynApply f a = exec as applied 
    | otherwise = Left "Some argument was the wrong type. XXX we can thread the arg number through if desired" 


main :: IO() 
main = 
    do print (exec [mkBox (1::Int), mkBox (2::Int)] (mkBox ((+) :: Int -> Int -> Int)) :: Either String Int) 
    print (exec [mkBox (1::Int)] (mkBox (last :: [Int] -> Int)) :: Either String Int) 
    print (exec [mkBox (1::Int)] (mkBox (id :: Int -> Int)) :: Either String Double) 
+0

你爲什麼使用'unsafeCoerce'?經典的「盒子」是「數據盒子=全部」。 Typeable a => Box a',它可讓您使用'cast','eqT'或'gcast'正確完成工作。是的,這些最終建立在'unsafeCoerce'(在Data.Typeable的實現中),但沒有一個應該關心用戶。如果您喜歡,可以使用新的「Type.Reflection」正確編寫更像您的盒子樣式。 – dfeuer

+0

事實上,我在使用動態的版本中隱式地使用反射模塊。 –

1

以下是一種使用帶有一個擴展名的類型類的方法。

{-# LANGUAGE FlexibleInstances #-} 

的想法是一個Function類型的類中定義exec

data Data = DInt Int | DBool Bool | DChar Char deriving (Show) 
data Error = TypeError String Data | MissingArg String | ExtraArgs 
      deriving (Show) 

class Function a where 
    exec :: a -> [Data] -> Either Error Data 

,然後限定一對實例的每個Data構造函數,一個類型檢查並應用類型的參數,遞歸評估exec以繼續討論其餘參數:

instance Function r => Function (Int -> r) where 
    exec f (DInt x : xs) = exec (f x) xs 
    exec _ ( y : xs) = Left $ TypeError "DInt" y 
    exec _ []   = Left $ MissingArg "DInt" 

和另一個要漢DLE該類型的「終值」:

instance Function Int where 
    exec x [] = Right (DInt x) 
    exec _ _ = Left ExtraArgs 

您需要類似的樣板的BoolChar和所有其他支持的類型。 (其實,這多少樣板大概可以用一些輔助功能和/或可能通過引入第二DataType類型類與IntBoolChar實例中刪除,但我沒有工作了這一點。)

instance Function r => Function (Bool -> r) where 
    exec f (DBool x : xs) = exec (f x) xs 
    exec _ (  y : xs) = Left $ TypeError "DBool" y 
    exec _ []    = Left $ MissingArg "DBool" 
instance Function Bool where 
    exec x [] = Right (DBool x) 
    exec _ _ = Left ExtraArgs 

instance Function r => Function (Char -> r) where 
    exec f (DChar x : xs) = exec (f x) xs 
    exec _ (  y : xs) = Left $ TypeError "DChar" y 
    exec _ []    = Left $ MissingArg "DChar" 
instance Function Char where 
    exec x [] = Right (DChar x) 
    exec _ _ = Left ExtraArgs 

然後:

> exec f [DInt 1, DInt 2] 
Right (DInt 3) 
> exec g [DBool True, DInt 1, DInt 0] 
Right (DInt 1) 
> exec f [DInt 1, DChar 'a'] 
Left (TypeError "DInt" (DChar 'a')) 
> exec f [DInt 1] 
Left (MissingArg "DInt") 
> exec f [DInt 1, DInt 2, DInt 3] 
Left ExtraArgs 
> 

也許令人驚訝,exec本身包裝這些功能整合到同一類型的,所以你可以寫:

> let myFunctions = [exec f, exec g] 
> :t myFunctions 
myFunctions :: [[Data] -> Either Error Data] 
> (myFunctions !! 0) [DInt 1, DInt 2] 
Right (DInt 3) 
> 

它允許您操作這些函數作爲[Data] -> Either Error [Data]類型的第一類值。

+0

如果你不想要,你不需要使用'FlexibleInstances'。您可以添加一個輔助類'Arg a',其中exec':: Function r =>(a - > r) - > [Data] - > Error Data',然後使用實例'instance(Arg a,Function r )=>函數(a - > r)其中exec = exec''。 – dfeuer