您可以在类型类约束参数上对构造函数进行模式匹配吗?

发布于 2024-11-02 18:50:12 字数 705 浏览 5 评论 0原文

请参阅下面的代码示例。它不会编译。我原以为可能是因为测试函数中的第一个参数必须具有单一类型。但这没有意义,因为如果我不对其进行模式匹配,那么它就会编译,我可以使用 MyObj11 5MyObj21 5 来调用它,这是两个不同类型。

那么,是什么限制了您无法对具有类型类约束参数的构造函数进行模式匹配呢?或者有什么机制可以实现吗?

class SomeClass a where toString :: a -> String

instance SomeClass MyType1 where toString v = "MyType1"
instance SomeClass MyType2 where toString v = "MyType2"

data MyType1 = MyObj11 Int | MyObj12 Int Int 
data MyType2 = MyObj21 Int | MyObj22 Int Int 

test :: SomeClass a => a -> String
test (MyObj11 x) = "11"
test (MyObj12 x y) = "12" -- Error here if remove 3rd line: rigid type bound error
test (MyObj22 x y) = "22" -- Error here about not match MyType1.

See code example below. It won't compile. I had thought that maybe it's because it has to have a single type for the first parameter in the test function. But that doesn't make sense because if I don't pattern match on it so it will compile, I can call it with both MyObj11 5 and MyObj21 5 which are two different types.

So what is it that restricts so you can't pattern match on constructors with a type class constrained parameter? Or is there some mechanism by which you can?

class SomeClass a where toString :: a -> String

instance SomeClass MyType1 where toString v = "MyType1"
instance SomeClass MyType2 where toString v = "MyType2"

data MyType1 = MyObj11 Int | MyObj12 Int Int 
data MyType2 = MyObj21 Int | MyObj22 Int Int 

test :: SomeClass a => a -> String
test (MyObj11 x) = "11"
test (MyObj12 x y) = "12" -- Error here if remove 3rd line: rigid type bound error
test (MyObj22 x y) = "22" -- Error here about not match MyType1.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

醉南桥 2024-11-09 18:50:13

是什么限制使您无法在具有类型类约束参数的构造函数上进行模式匹配?

当您对显式构造函数进行模式匹配时,您将提交特定的数据类型表示。该数据类型并非在类的所有实例之间共享,因此根本不可能以这种方式编写适用于所有实例的函数。

相反,您需要将所需的不同行为与每个实例关联起来,如下所示:

class C a where 
    toString   :: a -> String
    draw       :: a -> String

instance C MyType1 where
    toString v = "MyType1"

    draw (MyObj11 x)   = "11"  
    draw (MyObj12 x y) = "12"

instance C MyType2 where
    toString v = "MyType2"

    draw (MyObj22 x y) = "22"

data MyType1 = MyObj11 Int | MyObj12 Int Int 
data MyType2 = MyObj21 Int | MyObj22 Int Int 

test :: C a => a -> String
test x = draw x

原始 test 函数的分支现在分布在实例中。

一些替代技巧涉及使用类关联数据类型(向编译器证明数据类型在所有实例之间共享),或视图模式(它可以让您概括模式匹配)。


视图模式

我们可以使用视图模式来稍微清理模式匹配和类型类实例之间的连接,从而允许我们通过共享类型上的模式匹配来近似跨实例的模式匹配。

这是一个示例,我们编写一个函数,有两种情况,让我们可以对类中的任何内容进行模式匹配。

{-# LANGUAGE ViewPatterns #-}

class C a where 
    view       :: a -> View

data View = One Int
          | Two Int Int

data MyType1 = MyObj11 Int | MyObj12 Int Int 

instance C MyType1 where
    view (MyObj11 n) = One n
    view (MyObj12 n m) = Two n m

data MyType2 = MyObj21 Int | MyObj22 Int Int 

instance C MyType2 where
    view (MyObj21 n)   = One n
    view (MyObj22 n m) = Two n m

test :: C a => a -> String
test (view -> One n)   = "One " ++ show n
test (view -> Two n m) = "Two " ++ show n ++ show m

请注意 -> 语法如何让我们在每个实例中回调正确的 view 函数,查找每种类型的自定义数据类型编码,以便进行模式匹配在它上面。

设计挑战是提出一个视图类型来捕获您感兴趣的所有行为变体。

在您最初的问题中,您希望每个构造函数都有不同的行为,因此实际上没有理由使用视图类型(调度直接针对每个实例中的行为已经足够好了)。

what is it that restricts so you can't pattern match on constructors with a type class constrained parameter?

When you pattern match on an explicit constructor, you commit to a specific data type representation. This data type is not shared among all instances of the class, and so it is simply not possible to write a function that works for all instances in this way.

Instead, you need to associate the different behaviors your want with each instance, like so:

class C a where 
    toString   :: a -> String
    draw       :: a -> String

instance C MyType1 where
    toString v = "MyType1"

    draw (MyObj11 x)   = "11"  
    draw (MyObj12 x y) = "12"

instance C MyType2 where
    toString v = "MyType2"

    draw (MyObj22 x y) = "22"

data MyType1 = MyObj11 Int | MyObj12 Int Int 
data MyType2 = MyObj21 Int | MyObj22 Int Int 

test :: C a => a -> String
test x = draw x

The branches of your original test function are now distributed amongst the instances.

Some alternative tricks involve using class-associated data types (where you prove to the compiler that a data type is shared amongst all instances), or view patterns (which let you generalize pattern matching).


View patterns

We can use view patterns to clean up the connection between pattern matching and type class instances, a little, allowing us to approximate pattern matching across instances by pattern matching on a shared type.

Here's an example, where we write one function, with two cases, that lets us pattern match against anything in the class.

{-# LANGUAGE ViewPatterns #-}

class C a where 
    view       :: a -> View

data View = One Int
          | Two Int Int

data MyType1 = MyObj11 Int | MyObj12 Int Int 

instance C MyType1 where
    view (MyObj11 n) = One n
    view (MyObj12 n m) = Two n m

data MyType2 = MyObj21 Int | MyObj22 Int Int 

instance C MyType2 where
    view (MyObj21 n)   = One n
    view (MyObj22 n m) = Two n m

test :: C a => a -> String
test (view -> One n)   = "One " ++ show n
test (view -> Two n m) = "Two " ++ show n ++ show m

Note how the -> syntax lets us call back to the right view function in each instance, looking up a custom data type encoding per-type, in order to pattern match on it.

The design challenge is to come up with a view type that captures all the behavior variants you're interested in.

In your original question, you wanted every constructor to have a different behavior, so there's actually no reason to use a view type (dispatching directly to that behavior in each instance already works well enough).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文