我对Haskell相对较新,并试图解决如何使用forall将我的类型“封装”在非类型化数据中:
我不知道这是否法律的,但我在尝试:
data Value = Value (forall a b c. (Basis a, Basis b, Basis c) => Val a b c)
data Operation = Operation (forall a. (Basis a) => Op a a)
字符串
我想知道如何将操作应用于值,给定值的“a”类型必须匹配操作中的“a”:
opApply :: Operation -> Value -> IO()
opApply (Operation g) (Value q) = app1 g q
型
在这里,'app1'正确地警告类型错误。
我尝试使用约束和类型族,但无法克服。
1条答案
按热度按时间xlpyo6sf1#
最有可能的情况是,您并不想这样做。如果您将类型“封装”在非类型化数据中,因为您认为数据类型是一个实现细节,应该对类型的“公共
Basis
接口”隐藏起来,或者因为您认为在类型签名中编写Value
而不是Val a b c
会更方便,或者是因为您模糊地认为需要以统一的方式处理大量不同类型的Val
和Op
,而正确的方法是构造统一的、非参数化的Value
和Operator
类型,那你就做错了。在Haskell中表达
opApply
的通常正确方法是写如下:字符串
可能会将
Basis
约束添加到opApply
类型签名,如:型
或者是:
型
视您需要在
a
、b
和c
类型上执行Basis
作业而定。a
、b
和c
类型变量在类型签名中是可见的,并且是类型的“接口”的一部分,这是一个特性,而不是一个bug。这使您可以简洁地表达编译时约束,即Op a a
中的两个类型参数是相同的类型a
,并且该类型还必须与Val a b c
中的a
匹配,opApply
操作才有效。如果有任何可能的方法可以使用这些简单的类型来编写程序,那么这就是您应该编写程序的方式。另一方面,在一项研究中,如果您发现需要处理
Val
或Op
,而这些类型参数在运行时之前是未知的--通常只有在您重新从控制台或文件或某些其他输入源阅读输入,并将其解析为Val
或Op
,其中解析器可能会为一个输入或一个Val String Int Double
表示另一个输入--那么您可能希望使用“存在”类型,例如:型
请注意
forall
的位置和此处的限制:它们位于构造函数的外部,而不是构造函数的内部,这与您最初问题中的示例不同。这些
SomeVal
和SomeOp
存在类型可用于保存未指定类型的Val
和Op
值,这些值(只能)通过Basis
接口进行操作。存在主义者很难相处,所以除非迫不得已,否则你不会想这么做。对它们进行操作的代码变得非常复杂,非常快。假设Typeable
是Basis
的一个超类(在完全通用的情况下进行这种类型匹配是必需的),尝试通过这些 Package 器将Op a a
与Val a b c
“匹配”的opApply
将采用以下形式:型
真恶心。
您在问题中定义的类型:
型
是秩2类型,而不是存在类型。它们可能是有用的,尽管它们可能并不意味着你所想的那样。
如果您希望类型表示抽象的
Value
或Operation
值,而这些值可以用Basis
的任何类型表示,以便单个特定值:型
表示一个抽象的
Val
,它可以转换为Val Int Int Int
、Val Char Bool Bool
或任何其他Basis
示例,这取决于它在代码中的使用位置,与单个特定的数字文本的方式相同:型
可以是
Int
、Double
、Word8
或任何其他具有Num
示例的类型,具体取决于它在给定程序中出现的位置,* 则 * 这些等级为2的类型可能有意义。在这种情况下,
opApply
会使用a
、b
和c
的呼叫端指定型别,将Operator
或Value
结合起来:型
在使用
opApply
的程式码中,您需要提供一组特定的Basis
型别做为参数:型
同样,给定的
o :: Operator
或v :: Value
将是类型不可知的--opApply
的调用者可以提供任何有效的目标Basis
类型,而o
和v
类型将不得不强制opApply
生成所请求的Basis
类型的值。因此,这些类型(1)是可能的,(2)可能是有用的,尽管我不确定它们是否与您在这里尝试做的相匹配。