[英]Pattern matching against value constructors in type variables / some sort of flexible function-polymorphism in Haskell
Let's say I have the algebraic data type of a binary tree defined as: 假设我的二叉树的代数数据类型定义为:
data Tree a = Empty | Node a (Tree a) (Tree a)
To build trees, I now want to define a function (-<)
such that I can create trees like this: 为了构建树,我现在想定义一个函数(-<)
这样我就可以创建树:
1-<(2,3) 1-<(2-<(3,4),5) 1-<(Empty,2-<(4,5))
1 1 1
/ \ / \ \
2 3 2 5 2
/ \ / \
3 4 4 5
It seems it impossible! 看来不可能! My reasoning is that it must have type signature a -> (a, a) -> Tree a)
. 我的理由是它必须具有类型签名a -> (a, a) -> Tree a)
。 I then thought that it might be possible to interpret something of type a
either as Tree b
for some other type b
or, if that fails, plainly as a
. 那么我认为这也许可以解释型的东西a
无论是作为Tree b
的一些其他类型的b
或者,如果失败,显然是a
。 In the same manner I tried to define 我尝试以相同的方式定义
f :: a -> Int
f (Just _) = 1
f _ = 0
which would be a function that told me whether a value is of type Maybe a
and constructed by Just
or not. 这将是一个告诉我值是否为Maybe a
类型且由Just
构造或不构造的函数。 But this doesn't work – ghc then wants the type signature to be Maybe a -> Int
. 但这是行不通的-ghc然后希望类型签名为Maybe a -> Int
a- Maybe a -> Int
。
Unless I don't know of any special feature in Haskell, what I want is impossible. 除非我不知道Haskell的任何特殊功能,否则我想要的是不可能的。 My questions now are: 我的问题是:
(Right 2 -<(Right 3, Left (Right 4 -<(Right 1, Right 5)))
is no solution.) (我想简洁,所以例如,写(Right 2 -<(Right 3, Left (Right 4 -<(Right 1, Right 5)))
是没有解决的。) PS I really don't know how to title this question. 附言:我真的不知道该如何称呼这个问题。
You could just create three different operators: 您可以创建三个不同的运算符:
(-<) :: a -> (a, a) -> Tree a
p -< (l, r) = Node p (Node l Empty Empty) (Node r Empty Empty)
(-<\) :: a -> (Tree a, a) -> Tree a
p -<\ (lt, r) = Node p lt (Node r Empty Empty)
(-</) :: a -> (a, Tree a) -> Tree a
p -</ (l, rt) = Node p (Node l Empty Empty) rt
then your trees can be expressed as 那么你的树可以表示为
1-<(2,3)
1-<\(2-<(3,4),5)
and 和
1-<\(Empty,2-<(4,5))
The function you seek can't have type a -> (a, a) -> Tree a)
in Haskell; 寻找的函数在Haskell中不能输入a -> (a, a) -> Tree a)
; it simply isn't well-typed at all. 它根本没有被很好地键入。 The reason is that those a
s could be any type at all, so you can't use their values in any operation that needs a property of any specific type (such as checking whether they are an Empty
or Node
value). 其原因是,那些a
小号可以是任何类型可言,所以你不能在需要的任何特定类型的属性(如检查是否是一个任何操作中使用他们的价值观Empty
或Node
值)。
The function you're asking for is also ambiguously defined when you remember that you can put trees inside trees, and so a possibility for those a
s could actually be Tree b
. 当您记得可以将树放入树中时,所要查询的函数也被模棱两可,因此,那些a
s的可能实际上是Tree b
。 For example, if I tried Empty -< (Empty, Empty)
to make a Tree (Tree t)
(for some t
), is this supposed to return Node Empty Empty Empty
where I interpret the (Empty, Empty)
as a pair of trees, or Node (Node Empty Empty) (Node Empty Empty)
where I interpret the (Empty, Empty)
as a pair of "bare values" that need to be turned into leaf nodes? 例如,如果我尝试用Empty -< (Empty, Empty)
制作一Tree (Tree t)
(对于某些t
),这是否应该返回Node Empty Empty Empty
,我将(Empty, Empty)
解释为一对树,还是Node (Node Empty Empty) (Node Empty Empty)
,其中我将(Empty, Empty)
解释为一对需要变为叶节点的“裸值”? (Or one of the other permutations?) (或其他排列之一?)
You could design a language so that this ambiguity is resolvable, but Haskell avoids the issue by making values in a completely arbitrary type (like a
) completely opaque. 您可以设计一种语言来解决这种歧义,但是Haskell通过使值完全不透明(完全像a
任意类型)(例如)避免了这个问题。 You basically can't do anything at all with such a value other than pass it on to another function that accepts a completely arbitrary type; 除了将其传递给接受完全任意类型的另一个函数外,您基本上不能用这种值做任何事情。 this can seem limiting, but this is actually a key part of how Haskell types work and responsible for a lot of the safety guarantees that make a lot of generic library code work. 这似乎是有限的,但这实际上是Haskell类型如何工作的关键部分,并负责使许多通用库代码起作用的许多安全保证。
So what you need is for every position to be expecting an a
, or a Tree a
; 因此,您需要的是每个职位都期望a
或Tree a
; it's not possible to accept an "anything" and see whether it is a Tree a
. 不可能接受“任何东西”,看看它是否是Tree a
。 One way to do that is to use a family of functions that covers all the possibilities, as in Lee's answer. 一种解决方法是使用涵盖所有可能性的一系列功能,如Lee的回答。 That's a lot less fun if there are more than two positions! 如果职位多于两个,那就太有趣了! Another possibility is to only accept trees and use a projection function that turns "bare values" into singleton trees 1 . 另一种可能性是仅接受树并使用将“裸值”转换为单例树1的投影函数。 eg 例如
data Tree a = Empty | Node a (Tree a) (Tree a)
deriving (Show, Eq, Ord)
(-<) :: a -> (Tree a, Tree a) -> Tree a
x -< (l, r) = Node x l r
t :: a -> Tree a
t x = Node x Empty Empty
Then you are effectively resolving the ambiguity I mentioned above by using t
to "tag" everything that needs to be turned into a tree, while everything without the tag must already be a tree of the correct type. 然后,您可以通过使用t
对需要转换为树的所有内容进行“标记”来有效地解决我上面提到的歧义,而没有标记的所有内容都必须已经是正确类型的树。
Your examples would then be written as: 您的示例将写为:
*Main> 1 -< (t 2, t 3)
Node 1 (Node 2 Empty Empty) (Node 3 Empty Empty)
*Main> 1 -< (2 -< (t 3, t 4), t 5)
Node 1 (Node 2 (Node 3 Empty Empty) (Node 4 Empty Empty)) (Node 5 Empty Empty)
*Main> 1 -< (Empty, 2 -< (t 4, t 5))
Node 1 Empty (Node 2 (Node 4 Empty Empty) (Node 5 Empty Empty))
1 This is basically the return
function you would write if you were making Tree
into a monad, but return
is a bit long to sprinkle all through your succinct tree expressions, and seems unrelated to tree-making, so I think a better/shorter name is helpful here ( t
isn't necessarily great, but it's short and I wasn't feeling terribly inspired). 1这基本上是您将Tree
变成monad时要编写的return
函数,但是return
有点长,无法遍历简洁的树表达式,而且似乎与制树无关,因此我想一个更好/更短的名字在这里有帮助的( t
不一定很大,但它的短,我并没有感觉非常的启发)。
You can write a class which does what you want pretty easily: 您可以编写一个可以轻松实现所需功能的类:
{-# LANGUAGE MultiParamTypeClasses,
FlexibleInstances, FunctionalDependencies, TypeFamilies, OverlappingInstances, UndecidableInstances #-}
data Tree a = Empty | Node a (Tree a) (Tree a) deriving Show
class Leaf a b | a -> b where
leaf :: a -> b
instance Leaf (Tree a) (Tree a) where leaf = id
instance (d ~ Tree c) => Leaf c d where leaf a = Node a Empty Empty
mkTree a (b,c) = Node a (leaf b) (leaf c)
The only problem with this approach is that due to defaulting rules, things like mkTree 1 (1,2)
will fail because the integer literals are polymorphic, whereas mkTree (1 :: Int) ((1 :: Int),(2 :: Int))
will work. 这种方法的唯一问题是,由于默认规则,诸如mkTree 1 (1,2)
类的东西将失败,因为整数文字是多态的,而mkTree (1 :: Int) ((1 :: Int),(2 :: Int))
将起作用。 You can make it 'work' with fully polymorphic types by turning on IncoherentInstances
but this makes things behave even more strangely so it isn't the best solution. 您可以通过打开IncoherentInstances
使它与完全多态类型一起工作,但这会使事情表现得更加奇怪,因此它不是最佳解决方案。
You mentioned you want a succinct syntax and the 2nd option is only slightly less succinct but will always work when it should instead of giving cryptic type errors, including with fully polymorphic types: 您提到要使用简洁的语法,第二个选项的简洁性稍差一些,但始终可以正常工作,而不必给出含糊的类型错误,包括完全多态类型:
{-# LANGUAGE
MultiParamTypeClasses
, FlexibleInstances
, FunctionalDependencies
#-}
data Tree a = Empty | Node a (Tree a) (Tree a) deriving Show
data L a = L a
class Leaf' a b | a -> b where
leaf' :: a -> b
instance Leaf' (L a) (Tree a) where
leaf' (L a) = Node a Empty Empty
instance Leaf' (Tree a) (Tree a) where
leaf' = id
mkTree' a (b,c) = Node a (leaf' b) (leaf' c)
>1-<(L 2,L 3)
Node 1 (Node 2 Empty Empty) (Node 3 Empty Empty)
>1-<(2-<(L 3,L 4),L 5)
Node 1 (Node 2 (Node 3 Empty Empty) (Node 4 Empty Empty)) (Node 5 Empty Empty)
>1-<(Empty,2-<(L 4,L 5))
Node 1 Empty (Node 2 (Node 4 Empty Empty) (Node 5 Empty Empty))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.