【问题标题】:Haskell: Function Application on Wrapped DataHaskell:包装数据上的函数应用
【发布时间】:2017-12-28 12:10:02
【问题描述】:

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

数据需要以通用方式存储,作为相同类型,以便在传递给 exec 函数之前可以存储在列表中。

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

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

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

如果函数期待一个 Bool 但找到一个 Int,则会引发错误等。

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

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

【问题讨论】:

  • 你熟悉Data.Dynamic吗?

标签: haskell


【解决方案1】:

认为你所要求的完全是非惯用的。我将提出一个你永远不应该使用的答案,因为如果它是你想要的,那么你就是以错误的方式解决问题。

一个糟糕但有趣的解决方案

概述: 我们将构建盒子 - 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)

【讨论】:

  • 你为什么使用unsafeCoerce?经典的Boxdata Box = forall a. Typeable a =&gt; Box a,它允许您使用casteqTgcast 来正确完成工作。是的,这些最终都建立在unsafeCoerce(在Data.Typeable 的实现中),但这些都不应该与用户有关。如果您愿意,可以使用新的Type.Reflection 正确编写更像您的框样式的东西。
  • 确实,我在使用动态的版本中隐式使用反射模块。
【解决方案2】:

readMaybeText.Read 包中。我会尝试读取输入,如果返回 Nothing 尝试解析另一种类型。您必须遵守命令才能这样做。比如先Int,再Bool,以此类推

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

【讨论】:

  • 此提案使用String 作为穷人的Data.Dynamic.Dynamic 并解析(readMaybe)来代替显式的TypeRep。如果数据以字符串形式开始,这是一个很好的策略,但如果数据实际上是 Haskell 值,那么调用 show 只是为了统一类型有点笨拙。
【解决方案3】:

这是一种使用带有一个扩展的类型类的方法。

{-# 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"

另一个处理该类型的“最终值”:

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

BoolChar 以及所有其他受支持的类型都需要类似的样板。 (实际上,可以使用一些辅助函数和/或通过引入第二个带有IntBoolChar 实例的DataType 类型类来删除大部分样板,但我还没有解决这个问题.)

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] -&gt; Either Error [Data] 类型的第一类值来操作。

【讨论】:

  • 如果您不想使用,则无需使用FlexibleInstances。您可以改为添加一个辅助类class Arg a where exec' :: Function r =&gt; (a -&gt; r) -&gt; [Data] -&gt; Either Error Data,然后使用实例instance (Arg a, Function r) =&gt; Function (a -&gt; r) where exec = exec'
猜你喜欢
  • 1970-01-01
  • 2010-09-25
  • 2014-11-01
  • 2018-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-23
相关资源
最近更新 更多