我认为你所要求的完全是非惯用的。我将提出一个你永远不应该使用的答案,因为如果它是你想要的,那么你就是以错误的方式解决问题。
一个糟糕但有趣的解决方案
概述: 我们将构建盒子 - any 类型的值。这些框将携带值和类型表示,我们可以用于相等性检查,以确保我们的函数应用程序和返回类型都是正确的。然后,我们在应用函数之前手动检查类型表示(表示类型的值,在编译时丢失)。记住函数和参数类型是不透明的——它们在编译时被删除了——所以我们需要使用有罪的函数unsafeCoerce。
所以首先我们需要存在类型、可类型化和不安全的强制:
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE TypeApplications #-}
import Data.Typeable
import Unsafe.Coerce
盒子是我们的存在:
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,这个 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)