我认为你所要求的是完全不地道。我将提出一个你永远不应该使用的答案,因为如果它是你想要的,那么你正在以错误的方式解决问题。
一个坏的,但有趣的解决方案
概述:我们将构建盒 - 的任何类型的值。这些框将携带我们可用于相等性检查的值和类型表示,以确保我们的函数应用程序和返回类型都是正确的。然后,我们在应用函数之前手动检查类型表示(表示类型的值,这些值在编译时丢失)。记住函数和参数类型是不透明的 - 它们在编译时被擦除 - 所以我们需要使用有罪功能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)
您是否熟悉'Data.Dynamic'? –