我有一个包含很多不同功能列表的系统。我想要用户 能够从shell传递数据到这些函数。如果它们传递的数据类型错误,则在执行该函数时应显示错误。
数据需要以一般方式存储,作为相同的类型,以便在传递到exec函数之前将其存储在列表中。
data Data = DInt Int | DBool Bool | DChar Char .....
有没有办法将数据列表传递给像这样的函数?
exec :: [Data] -> (wrapped up function) -> Either Error Data
如果函数期望Bool但是找到了Int,则会抛出错误等。
该函数必须包含在某种结构中以允许此应用程序,但我不确定是否有一种简单的方法可以实现这种行为。
谢谢,第二次尝试写这个,请求任何澄清。
答案 0 :(得分:4)
我要求的认为完全不是惯用的。我将提出一个你永远不应该使用的答案,因为如果它是你想要的那样你就会以错误的方式解决问题。
糟糕但有趣的解决方案
概述:我们将构建框 - 任何类型的值。这些框将包含值和类型表示,我们可以使用它们进行相等性检查,以确保我们的函数应用程序和返回类型都是正确的。然后,我们在应用函数之前手动检查类型表示(表示在编译时丢失的类型的值)。记住函数和参数类型是不透明的 - 它们在编译时被删除了 - 所以我们需要使用有罪的函数unsafeCoerce
。
首先,我们需要存在类型,类型和不安全的强制:
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE TypeApplications #-}
import Data.Typeable
import Unsafe.Coerce
The Box是我们的存在主义者:
data Box = forall a. Box (TypeRep, a)
如果我们使用安全API制作模块,我们想要制作一个智能构造函数:
-- | 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
,因为你可以使用Data.Dynamic
,所以这个API比你需要的更具教育性和简洁性。例如(我也改变了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)
答案 1 :(得分:2)
readMaybe
位于Text.Read
包中。我会尝试读取输入,如果返回Nothing
尝试解析另一种类型。你必须下令这样做。例如,首先是Int
,然后是Bool
等等。
http://hackage.haskell.org/package/base-4.10.1.0/docs/Text-Read.html#v:readMaybe
答案 2 :(得分:1)
这是一种使用带有一个扩展名的类型类的方法。
{-# LANGUAGE FlexibleInstances #-}
我们的想法是在exec
类型类中定义Function
:
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"
和另一个处理该类型的“最终值”:
instance Function Int where
exec x [] = Right (DInt x)
exec _ _ = Left ExtraArgs
您需要Bool
和Char
以及所有其他支持类型的类似样板。 (实际上,很可能通过一些辅助函数和/或可能通过引入带有DataType
,Int
和Bool
实例的第二个Char
类型类来删除大部分样板文件,但我没有这样做。)
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]
的第一类值进行操作。