简体   繁体   English

将模式匹配限制为构造函数的子集

[英]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类型族从VariantCodeDelete 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: 该解决方案的缺点是:

  • Different syntax for handling the shrunk type. 处理收缩类型的语法不同。
  • Worse type errors. 更糟糕的类型错误。
  • Slower at runtime, not as efficient as native sum types. 在运行时较慢,不如本机和类型有效。
  • The usual bane of extensible record libraries: very slow compilation times for large types. 通常的可扩展记录库的祸害:大型类型的编译时间非常慢

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.

相关问题 自定义构造函数 Haskell 上的模式匹配 - Pattern matching on custom constructors Haskell 模式匹配与构造函数 - Pattern matching vs. constructors 相当于Haskell模式匹配的Scala构造函数 - Scala equivalent to Haskell pattern Matching on constructors 代数类型数据构造函数的“模式匹配” - “Pattern matching” of algebraic type data constructors 在Haskell中对我的数据子集进行模式匹配 - Pattern matching on a subset of my data in haskell 在Haskell模块中是否可以导出构造函数以进行模式匹配,但不能用于构造? - Is it possible to export constructors for pattern matching, but not for construction, in Haskell Modules? Applicative(Functor)类型的简单推广;构造函数上的模式匹配 - a simple generalisation of the Applicative (Functor) type-class; pattern matching on constructors Haskell / GHC:匹配具有相同模式的多个一元构造函数 - Haskell/GHC: Matching multiple unary constructors with the same pattern 针对类型变量/ Haskell中某种灵活的函数多态性的值构造函数进行模式匹配 - Pattern matching against value constructors in type variables / some sort of flexible function-polymorphism in Haskell Haskell是否为许多可能的数据构造函数提供了模式匹配的习惯用法? - Does Haskell provide an idiom for pattern matching against many possible data constructors?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM