Haskell:函数在包装数据上的应用 [英] Haskell: Function Application on Wrapped Data

查看:106
本文介绍了Haskell:函数在包装数据上的应用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



数据需要以一般方式存储,如相同的类型,因此它可以在传递到exec函数之前存储在列表中。

  data Data = DInt Int | DBool布尔| DChar Char ..... 

有没有办法让数据列表传入像这样的函数?

  exec :: [Data]  - > (包装功能) - >错误数据

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

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

/ p>

谢谢,第二次试图写这个,所以请请求澄清。

解决方案

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

坏但有趣的解决方案
$ b 概述:我们将构建box - 任何类型的值。这些框将携带我们可用于相等性检查的值和类型表示,以确保我们的函数应用程序和返回类型都是正确的。然后,我们在应用函数之前手动检查类型表示(表示类型的值,这些值在编译时丢失)。记住函数和参数类型是不透明的 - 它们在编译时被擦除 - 所以我们需要使用有罪函数 unsafeCoerce



因此,首先我们需要存在类型,类型和不安全胁迫:

  { - #LANGUAGE ExistentialQuantification # - } 
{ - #LANGUAGE TypeApplications# - }
import Data.Typeable
import Unsafe.Coerce

箱子是我们的存在:

  data Box = forall a。 Box(TypeRep,a)

如果我们使用安全的API创建模块,我们希望做一个聪明的构造函数:

   -  |将类型转换为值的框和值的类型。 
mkBox :: Typeable a => a - > Box
mkBox a = Box(typeOf a,a)

您的 exec 函数现在不需要获取这个丑陋的总和类型( Data )的列表,而是可以取一个框列表和函数,以盒子的形式,然后将每个参数应用于函数,以获得结果。注意调用者需要静态地知道返回类型 - 由代理参数表示 - 否则我们必须返回一个Box,因为结果很没用。

  exec :: Typeable a 
=> [Box] - ^参数
- >框 - ^功能
- >代理a
- >字符串a
exec [](Box(fTy,f))p
| fTy == typeRep p = Right $ unsafeCoerce f
- ^^该函数已完全应用。如果它是期望的
类型 - 那么调用者可以返回该值。
|否则=左最终值与代理类型不匹配。
exec((Box(aTy,a)):as)(Box(fTy,f))p
| Just appliedTy< - funResultTy fTy aTy = exec as(Box(appliedTy,(unsafeCoerce f)(unsafeCoerce a)))p
- ^^还有至少一个参数
|否则= Left某些参数是错误的类型,如果需要,我们可以使用线程编号通过参数
- ^^函数期望使用不同的参数类型或完全适用(提供的参数太多!)

我们可以简单地测试三个结果:

  main :: IO()
main =
打印$ exec [mkBox(1 :: Int),mkBox(2 :: Int)](mkBox (+):: Int - > Int - > Int))(Proxy @Int)
print $ exec [mkBox(1 :: Int)](mkBox(last :: [Int] - & ))(Proxy @Int)
print $ exec [mkBox(1 :: Int)](mkBox(id :: Int - > Int))(Proxy @Double)

收益率:

 右3 
Left某些参数是错误的类型,如果需要,可以通过XXX来使用arg编号
最终值与代理类型不匹配。

编辑:我应该提及 Box 和由于您可以使用 Data.Dynamic ,因此此API更具教育意义并且不太需要简洁。例如(因为可以推断代理,我也改变了API):

  { - #LANGUAGE ExistentialQuantification# - } 
{ - #LANGUAGE GADTs# - }
import Data.Dynamic
import Type.Reflection

类型Box =动态

- 将类型转换为值的框和
- 值的类型。
mkBox :: Typeable a => a - > Box
mkBox = toDyn

exec ::可键入a
=> [Box] - ^参数
- >框 - ^功能
- >字符串a
exec [] f =来自动态f的
的情况只要x - > Right x
Nothing - >左最终类型不匹配代理
exec(a:as)f
|刚应用< - dynApply f a = exec作为应用
|否则=左一些参数是错误的类型XXX,如果需要,我们可以对参数号进行编程


main :: IO()
main =
(int [int])或者int(int))
(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)


I have a system with a long list of different functions. I would like the user to be able to pass data into these functions from a shell. If the data that they pass is of the wrong type, a error should be displayed when the function is executed.

Data needs to be stored in a general way, as the same type, so that it can be stored in lists before being passed into an exec function.

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

Is there a way for a list of Data, to be passed into a function like such?

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

If the function was expecting a Bool but an Int was found, an error is thrown etc.

The function would have to wrapped in some sort of structure to allow this application but I'm not sure if there's an easy way to achieve this kind of behaviour.

Thanks, second trying trying to write this so please ask for any clarification.

解决方案

What I think you are asking for is entirely non-idiomatic. I'm going to present an answer that you should never use because if it is what you want then you are solving the problem the wrong way.

A Bad But Fun Solution

Overview: We will be constructing boxes - values of any type. These boxes will carry both the value and a type representation we can use for equality checks to ensure our function applications and return types are all correct. We then manually check the type representations (values that represent the types, which were lost at compile time) before applying the function. Remember the function and the argument types are opaque - they've been erased at compile time - so we need to use the sinful function unsafeCoerce.

So to start with we need existential types, typeable and unsafe coerce:

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE TypeApplications #-}
import Data.Typeable
import Unsafe.Coerce

The Box is our existential:

data Box = forall a. Box (TypeRep, a)

If we were making a module with a safe API we'd want to make a smart constructor:

-- | 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)

Your exec function now doesn't need to take a list of this ugly sum type (Data) but can instead take a list of boxes and the function, in the form of a box, then apply each argument to the function one at a time to obtain the result. Notice the caller needs to statically know the return type - signified by the Proxy argument - or else we'd have to return a Box as the result which is pretty useless.

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!)

We can test the three outcomes simply:

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)

Yielding:

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."

EDIT: I should mention that Box and this API is more educational and less concise than needed since you can use Data.Dynamic. For example (I changed up the API too since proxy can be inferred):

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

这篇关于Haskell:函数在包装数据上的应用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆