【问题标题】:Pattern matching against value constructors in type variables / some sort of flexible function-polymorphism in Haskell与类型变量中的值构造函数的模式匹配/ Haskell 中某种灵活的函数多态性
【发布时间】:2014-01-30 20:27:31
【问题描述】:

假设我将二叉树的代数数据类型定义为:

data Tree a = Empty | Node a (Tree a) (Tree a)

为了构建树,我现在想定义一个函数 (-<) 以便我可以像这样创建树:

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

这似乎是不可能的!我的理由是它必须具有类型签名a -&gt; (a, a) -&gt; Tree a)。然后我认为有可能将a 类型的东西解释为Tree b 用于其他类型b,或者,如果失败,则简单地解释为a。以同样的方式我试图定义

f :: a -> Int
f (Just _) = 1
f _ = 0

这将是一个函数,它告诉我一个值是否属于Maybe a 类型并且是否由Just 构造。但这不起作用——ghc 然后希望类型签名为Maybe a -&gt; Int。

除非我不知道 Haskell 有什么特殊功能,否则我想要的都是不可能的。我现在的问题是:

  • 是吗?还是我错过了什么?
  • 什么是我想要的很好的近似值? (我想简洁,例如写(Right 2 -&lt;(Right 3, Left (Right 4 -&lt;(Right 1, Right 5))) 是没有解决办法的。)

附:我真的不知道如何命名这个问题。

【问题讨论】:

    标签: haskell algebraic-data-types


    【解决方案1】:

    您可以创建三个不同的运算符:

    (-<) :: 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
    

    那么你的树可以表示为

    1-<(2,3)
    1-<\(2-<(3,4),5)
    

    和

    1-<\(Empty,2-<(4,5))
    

    【讨论】:

    • 我也想过这个,而且这似乎是目前为止最好的解决方案。还需要第四个(-&lt;|):: a-&gt; (Tree a, Tree a) -&gt; Tree a。谢谢!我认为您的最后一个行不通,而您需要(-&lt;|)。
    【解决方案2】:

    你可以编写一个很容易做你想做的事的类:

    {-# 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)
    

    这种方法的唯一问题是,由于默认规则,像 mkTree 1 (1,2) 这样的东西会失败,因为整数文字是多态的,而 mkTree (1 :: Int) ((1 :: Int),(2 :: Int)) 会起作用。您可以通过打开 IncoherentInstances 使其对完全多态类型“工作”,但这会使事情变得更加奇怪,因此它不是最佳解决方案。

    你提到你想要一个简洁的语法,第二个选项只是稍微不那么简洁,但总是会在应该的时候起作用,而不是给出神秘的类型错误,包括完全多态的类型:

    {-# 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))
    

    【讨论】:

    • 我明天需要看看这个!现在我去睡觉。谢谢!
    • 好吧,我不明白这是怎么回事,但这看起来确实很有趣。但是,我注意到它有效。这很棒而且非常令人惊讶!很高兴知道您可以弯曲 ghc 来做这样的事情。谢谢!出于这个原因,我会接受这个答案——这是最有趣的一个,它正面地回答了我的第一个问题。
    • 如果你想有更好的理解,你应该阅读用户手册中关于函数依赖和多参数类型类的部分:haskell.org/ghc/docs/7.0.4/html/users_guide/…
    【解决方案3】:

    你寻找的函数在 Haskell 中不能有 a -&gt; (a, a) -&gt; Tree a) 类型;它根本就不是很好的类型。原因是那些as 可以是任何类型,因此您不能在需要任何特定类型属性的任何操作中使用它们的值(例如检查它们是Empty 还是Node值)。

    当您记得您可以将树放在树中时,您所要求的功能也很模糊,因此as 的可能性实际上可能是Tree b。例如,如果我尝试使用Empty -&lt; (Empty, Empty) 生成Tree (Tree t)(对于某些t),这是否应该返回Node Empty Empty Empty,我将(Empty, Empty) 解释为一对树,或者Node (Node Empty Empty) (Node Empty Empty)将(Empty, Empty) 解释为一对需要转换为叶节点的“裸值”? (或其他排列之一?)

    您可以设计一种语言来解决这种歧义,但 Haskell 通过使完全不透明的完全任意类型(如 a)的值避免了这个问题。除了将它传递给另一个接受完全任意类型的函数之外,您基本上不能对这样的值做任何事情;这似乎是限制性的,但这实际上是 Haskell 类型如何工作的关键部分,并负责许多使许多通用库代码工作的安全保证。

    因此,您需要让每个职位都期待a 或Tree a;不可能接受“任何东西”并查看它是否是Tree a。一种方法是使用涵盖所有可能性的一系列函数,如 Lee 的回答。如果有两个以上的职位,那就没那么有趣了!另一种可能性是仅接受树并使用将“裸值”转换为单例树的投影函数1。例如

    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
    

    那么你有效地解决了我上面提到的歧义,通过使用t 来“标记”需要变成树的所有内容,而没有标记的所有内容都必须已经是正确类型的树。

    你的例子会写成:

    *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 这基本上是return 函数,如果你把Tree 变成一个monad,你会写,但是return 有点长,要散布在你简洁的树表达式中,并且似乎与造树无关,所以我认为更好/更短的名称在这里会有所帮助(t 不一定很好,但它很短,我并没有受到很大的启发)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-01-29
      • 2017-08-04
      • 2018-06-09
      • 2016-04-26
      • 2015-10-19
      • 2013-08-14
      • 2022-01-18
      • 1970-01-01
      相关资源
      最近更新 更多