【问题标题】:Haskell Map TreeHaskell 映射树
【发布时间】:2020-06-16 04:03:27
【问题描述】:

我遇到了一个我无法真正解决的小问题。我想在树上映射一个函数。但是,当它编译时,它会在我想使用它时给出各种错误。我的想法是我运行“mapTree (+1) someTree”,它为每个值加 1

我的数据结构:

data BinTree a = Leaf | Branch (BinTree a) a (BinTree a) deriving Show

我的功能:

mapTree func Leaf = func []
mapTree func (Branch left a right) = Branch (mapTree func left) a (mapTree func right)

如果有人可以提示我做错了什么......

【问题讨论】:

  • 你得到的错误是什么?你能看懂吗?
  • func [] 不会是一棵树。你肯定想要mapTree func Leaf = Leaf
  • 第二行还有一个错误 - 不是编译时错误,而是一个逻辑错误,它将停止函数按预期工作。我会让你弄清楚那个:)
  • 哦,非常感谢!是的,我也发现了逻辑错误,当然也必须在值上运行函数......
  • 作为一般建议,定义顶级函数时始终从编写其类型开始。这将使 GHC 知道您打算做什么,从而产生更好的错误消息。

标签: haskell binary-tree map-function


【解决方案1】:

除了我的第一个答案之外,我想说的是,如果您想将a -> b 类型的某些函数应用于t a 类型的某个值(在我们的示例中,t 等于Tree),通常您可能希望为您的类型实现 Functor 实例(但只是小任务中的一个函数也可以)。这是FunctorTree 的实现:

instance Functor Tree where
  --fmap :: (a -> b) -> Tree a -> Tree b
  fmap f Leaf                  = Leaf
  fmap f (Branch left a right) = Branch (fmap f left) (f a) $ fmap f right

如你所见,fmap 就像你的mapTree,但现在你有Functor 类型的类,它更适合做这样的事情,因为Functor 实例在将来可能有用实现另一种类型的类。 使用示例:

*GHCi> fmap (*2) $ Branch (Branch Leaf 7 Leaf) (-3) Leaf
Branch (Branch Leaf 14 Leaf) (-6) Leaf

【讨论】:

  • 或者你可以写instance Functor BinTree where fmap = mapTree
  • 或者你可以在数据声明后写deriving Functor,编译器会为你写代码;)。 (最好先像这样自己做几次,直到你开始感到无聊)
【解决方案2】:

正如 chi 所指出的,最好从类型签名开始:

mapTree :: (a -> b) -> Tree a -> Tree b

如果你这样做了,你会得到两个类型错误,每个错误都指向你的代码中的一个不同的错误。第一个:

[1 of 1] 编译 Main (.code.tio.hs, .code.tio.o)

.code.tio.hs:6:21: 错误: • 无法将预期类型“树 b”与实际类型“b”匹配 ‘b’ 是一个刚性类型变量,由 类型签名: mapTree::forall a b. (a -> b) -> 树 a -> 树 b 在 .code.tio.hs:5:1-39 • 在表达式中:func [] 在“mapTree”的等式中:mapTree func Leaf = func [] • 相关绑定包括 func :: a -> b(绑定在 .code.tio.hs:6:9) mapTree :: (a -> b) -> 树 a -> 树 b(绑定在 .code.tio.hs:6:1) | 6 | mapTree func 叶 = func [] | ^^^^^^^

出现这种情况是因为第一种情况应该产生Tree b,但是将func 应用于参数只能产生b 类型的东西。

第二个:

.code.tio.hs:6:26: 错误: • 无法将预期类型“a”与实际类型“[a0]”匹配 ‘a’ 是一个刚性类型变量,由 类型签名: mapTree::forall a b. (a -> b) -> 树 a -> 树 b 在 .code.tio.hs:5:1-39 • 在‘func’的第一个参数中,即‘[]’ 在表达式中:func [] 在“mapTree”的等式中:mapTree func Leaf = func [] • 相关绑定包括 func :: a -> b(绑定在 .code.tio.hs:6:9) mapTree :: (a -> b) -> 树 a -> 树 b(绑定在 .code.tio.hs:6:1) | 6 | mapTree func 叶 = func [] | ^^

发生这种情况是因为当 func 期待 a 类型的参数时,您将 func 应用于某种类型的列表。

假设您通过将第一种情况更改为来修复这些错误

mapTree _func Leaf = Leaf

(我在func 前加了一个下划线。按照惯例,这表明它在这种情况下是故意不使用的。如果您应该启用编译器警告,这将阻止 GHC 警告您未使用的变量绑定.)

现在你会得到一个新的不同的错误:

.code.tio.hs:7:65:错误: • 无法将预期类型“b”与实际类型“a”匹配 ‘a’ 是一个刚性类型变量,由 类型签名: mapTree::forall a b. (a -> b) -> 树 a -> 树 b 在 .code.tio.hs:5:1-39 ‘b’ 是一个刚性类型变量,由 类型签名: mapTree::forall a b. (a -> b) -> 树 a -> 树 b 在 .code.tio.hs:5:1-39 • 在‘Branch’的第二个参数中,即‘a’ 在表达式中: 分支 (mapTree func left) a (mapTree func right) 在“mapTree”的等式中: mapTree func(左右分支) = 分支 (mapTree func left) a (mapTree func right) • 相关绑定包括 右 :: 树 a(绑定在 .code.tio.hs:7:29) a :: a(绑定在 .code.tio.hs:7:27) left :: 树 a(绑定在 .code.tio.hs:7:22) func :: a -> b(绑定在 .code.tio.hs:7:9) mapTree :: (a -> b) -> 树 a -> 树 b(绑定在 .code.tio.hs:6:1) | 7 | mapTree func (Branch left a right) = Branch (mapTree func left) a (mapTree func right) | ^

这是因为您忘记将func 应用于存储在节点中的值。您需要将 a 转换为 b 而您忘记了。

【讨论】:

    【解决方案3】:

    对于您的树类型,此类函数将如下所示:

    mapTree func Leaf = Leaf
    mapTree func (Branch left a right) = Branch (mapTree func left) (func a) (mapTree func right)
    

    您的错误是您不能将 (+1) 之类的功能应用于空列表。函数必须是 a -> b 类型。当你遇到Leaf 时,你不能对它做任何事情,因为在你的实现中 Leaf 不包含任何值,所以我们只返回Leaf。我们可以应用函数的唯一地方是Branch 的中心,这就是为什么我添加了应用这个函数到Branch 中的值

    在这里您可以找到工作示例:

    *GHCi> mapTree (+1) $ Branch Leaf 1 Leaf
    Branch Leaf 2 Leaf 
    
    *GHCi> mapTree (+1) Leaf
    Leaf
    

    【讨论】:

      猜你喜欢
      • 2011-01-15
      • 1970-01-01
      • 1970-01-01
      • 2012-07-24
      • 2020-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-04
      相关资源
      最近更新 更多