[英]Restrict Pattern Matching to Subset of Constructors
Say I have the following: 说我有以下内容:
data Type
= StringType
| IntType
| FloatType
data Op
= Add Type
| Subtract Type
I'd like to constrain the possible types under Subtract
, such that it only allows for int or float. 我想限制Subtract
下的可能类型,这样它只允许int或float。 In other words, 换一种说法,
patternMatch :: Op -> ()
patternMatch (Add StringType) = ()
patternMatch (Add IntType) = ()
patternMatch (Add FloatType) = ()
patternMatch (Subtract IntType) = ()
patternMatch (Subtract FloatType) = ()
Should be an exhaustive pattern match. 应该是一个详尽的模式匹配。
One way of doing this is to introduce separate datatypes for each operation, where it only has the allowed subtypes: 这样做的一种方法是为每个操作引入单独的数据类型,其中只有允许的子类型:
newtype StringType = StringType
newtype IntType = IntType
newtype FloatType = FloatType
data Addable = AddableString StringType | AddableInt IntType | AddableFloat FloatType
data Subtractable = SubtractableInt IntType | SubtractableFloat FloatType
data Op = Add Addable | Subtract Subtractable
However, this makes things a lot more verbose, as we have to create a new constructor name for each category. 但是,这会使事情变得更加冗长,因为我们必须为每个类别创建一个新的构造函数名称。 Is there a way to 'restrict' the possible constructors within a type without making an explicit subset? 有没有办法在不制作明确子集的情况下“限制”类型中可能的构造函数? Would this shorter with the use of DataKinds
? 使用DataKinds
会缩短DataKinds
吗? I'm a bit unsure as to how to make it more concise than just specifying new data for each constraint. 我有点不确定如何使它更简洁,而不仅仅是为每个约束指定新数据。
This question is an extension of my original question , where I asked about datakind unions. 这个问题是我原来问题的延伸,在那里我询问了datakind工会。 There were lots of good suggestions there, but unfortunately the unions don't work when pattern matching; 那里有很多很好的建议,但遗憾的是工会在模式匹配时不起作用; the compiler will still complain that the patterns are not exhaustive. 编译器仍然会抱怨这些模式并非详尽无遗。
This solution works but it might not be very practical in the end. 这个解决方案有效,但最终可能不太实用。 I'm using extensible variants from the red-black-record package. 我正在使用红黑记录包中的可扩展变体。
We define our types like this: 我们定义这样的类型:
{-# LANGUAGE DeriveGeneric, DataKinds, TypeFamilies, TypeApplications #-}
import GHC.Generics
import Data.RBR
data Ty
= StringTy ()
| IntTy ()
| FloatTy ()
deriving (Show,Generic)
instance ToVariant Ty
type ShrunkTy = Variant I (Delete "StringTy" () (VariantCode Ty))
data Op
= Add Ty
| Subtract ShrunkTy
Those annoying ()
parameters are there to overcome a limitation of red-black-record; 那些恼人的()
参数是为了克服红黑记录的限制 ; currently there are no instances of ToVariant
for sum types without constructor arguments. 目前没有没有构造函数参数的sum类型的ToVariant
实例。
Basically, we are removing the StringTy
constructor from the VariantCode
using the Delete
type family, and defining a Variant
with the reduced set of constructors. 基本上,我们使用Delete
类型族从VariantCode
中Delete
StringTy
构造函数,并使用减少的构造函数集定义Variant
。
We can then use the type like this: 然后我们可以使用这样的类型:
foo :: Op -> String
foo op =
case op of
Add ty ->
show "add" ++ show ty
Subtract ty ->
let cases = addCaseI @"IntTy" show
. addCaseI @"FloatTy" show
$ unit
in show "add" ++ eliminateSubset cases ty
Variant
s are eliminated using a Record
of handlers, constructed using the addCaseI
function. 使用addCaseI
函数构造的处理程序Record
消除 Variant
。 unit
is the empty Record
. unit
是空Record
。 If the Record
doesn't cover all cases that will result in a (pretty inscrutable) type error. 如果Record
未涵盖将导致(非常难以理解的)类型错误的所有情况。
The disadvantages with this solution are: 该解决方案的缺点是:
Other extensible record libraries ( vinyl + vinyl-generics , perhaps) might offer better ergonomics. 其他可扩展的记录库(可能是乙烯基 + 乙烯基仿制药 )可能提供更好的人体工程学。
Indexing a GADT with DataKinds
is one approach that may work, depending on your use case: 使用DataKinds
索引DataKinds
是一种DataKinds
的方法,具体取决于您的用例:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
-- The “group” of a type
data TypeGroup = NonNumeric | Numeric
-- A type indexed by whether it’s numeric
data Type (g :: TypeGroup) where
StringType :: Type 'NonNumeric
IntType :: Type 'Numeric
FloatType :: Type 'Numeric
data Op where
Add :: Type a -> Op
Subtract :: Type 'Numeric -> Op
Note that Add
works on either 'Numeric
or 'NonNumeric
Type
s because of the (existentially quantified) type variable a
. 请注意, Add
上的作品无论是'Numeric
或'NonNumeric
Type
由于S(存在性量化的)类型变量的a
。
Now this will work: 现在这将工作:
patternMatch :: Op -> ()
patternMatch (Add StringType) = ()
patternMatch (Add IntType) = ()
patternMatch (Add FloatType) = ()
patternMatch (Subtract IntType) = ()
patternMatch (Subtract FloatType) = ()
But adding this will fail: 但是添加它会失败:
patternMatch (Subtract StringType) = ()
With a warning about inaccessible code: Couldn't match type ''Numeric' with ''NonNumeric'
. 有关无法访问代码的警告: Couldn't match type ''Numeric' with ''NonNumeric'
。
If you need to add more type groupings, you may prefer to introduce type families to classify types instead, eg: 如果您需要添加更多类型分组,您可能更愿意引入类型系列来代替类型,例如:
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
-- An un-indexed type
data TypeTag = StringTag | IntTag | FloatTag
-- A type indexed with a tag we can dispatch on
data Type (t :: TypeTag) where
StringType :: Type StringTag
IntType :: Type IntTag
FloatType :: Type FloatTag
-- Classify a type as numeric
type family IsNumeric' (t :: TypeTag) :: Bool where
IsNumeric' 'StringTag = 'False
IsNumeric' 'IntTag = 'True
IsNumeric' 'FloatTag = 'True
-- A convenience synonym for the constraint
type IsNumeric t = (IsNumeric' t ~ 'True)
data Op where
Add :: Type t -> Op
Subtract :: IsNumeric t => Type t -> Op
This will produce the (slightly less descriptive) warning Couldn't match type ''True' with ''False'
if you add the redundant pattern. 这将产生(稍微不那么描述性)警告如果添加冗余模式,则Couldn't match type ''True' with ''False'
。
When working with GADTs you will often want existentials and RankNTypes
in order to work with runtime information; 使用GADT时,您通常需要存在性和RankNTypes
才能使用运行时信息; for that, patterns like these may prove useful: 为此,这些模式可能有用:
{-# LANGUAGE RankNTypes #-}
-- Hide the type-level tag of a type
data SomeType where
SomeType :: Type t -> SomeType
-- An unknown type, but that is known to be numeric
data SomeNumericType where
SomeNumericType :: IsNumeric t => Type t -> SomeNumericType
parseType :: String -> Maybe SomeType
parseType "String" = Just (SomeType StringType)
parseType "Int" = Just (SomeType IntType)
parseType "Float" = Just (SomeType FloatType)
parseType _ = Nothing
-- Unpack the hidden tag within a function
withSomeType :: SomeType -> (forall t. Type t -> r) -> r
withSomeType (SomeType t) k = k t
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.