Haskell:包装数据的函数应用

时间:2017-12-27 18:22:18

标签: haskell

我有一个包含很多不同功能列表的系统。我想要用户 能够从shell传递数据到这些函数。如果它们传递的数据类型错误,则在执行该函数时应显示错误。

数据需要以一般方式存储,作为相同的类型,以便在传递到exec函数之前将其存储在列表中。

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

有没有办法将数据列表传递给像这样的函数?

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

如果函数期望Bool但是找到了Int,则会抛出错误等。

该函数必须包含在某种结构中以允许此应用程序,但我不确定是否有一种简单的方法可以实现这种行为。

谢谢,第二次尝试写这个,请求任何澄清。

3 个答案:

答案 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

您需要BoolChar以及所有其他支持类型的类似样板。 (实际上,很可能通过一些辅助函数和/或可能通过引入带有DataTypeIntBool实例的第二个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]的第一类值进行操作。