【问题标题】:Check if list is flat in Haskell检查列表在 Haskell 中是否平坦
【发布时间】:2013-04-09 00:21:54
【问题描述】:

The Little Schemer中有一个函数可以检查列表是否平坦:

(define lat?
  (lambda (l)
    (cond
     ((null? l) #t)
     ((atom? (car l)) (lat? (cdr l)))
     (else #f))))

我正在尝试在 Haskell 中编写相同的递归函数,但没有成功:

is_lat :: [a] -> Bool
is_lat [] = True
is_lat ???

如何检查参数不是[[a]] 的形式?换句话说,[1,2,3] 是有效输入,但 [[1,3], [2,4]][[[1,2,3]]] 不是。

我想在接受列表的递归函数中进一步使用它,以确保我只处理平面列表。

编辑:我看到人们因为is_lat :: [a] -> Bool 类型签名而感到困惑。我现在同意,我不应该在运行时检查类型。但是,是否可以在编译时检查类型?如何使该功能仅适用于 flat 列表?还是我应该彻底改变我的思维方式?

【问题讨论】:

  • 问题是:你为什么要这个?你打算如何使用这个功能?
  • 如果它是一个列表,你能不能只检查每个元素?
  • 如果[[[1, 2, 3]]] 不是有效输入,则您的函数类型错误,您可能需要重新考虑您要完成的工作。
  • 列表平坦是什么意思?在 Haskell 中,所有列表必须具有相同类型的元素。
  • 为什么它只适用于平面列表?如果您需要某些元素,将其限制为固定类型(如[Int] -> Bool 或类型类Num a => [a] -> Bool)可能是有意义的,但如果它适用于任何元素类型,为什么元素类型不能是列表?

标签: list haskell recursion pattern-matching the-little-schemer


【解决方案1】:

这对于标准的 Haskell 列表来说既不可能也不必要,因为 Haskell 是强类型的;要么列表的所有元素本身都是列表(在这种情况下,类型是 [a] = [[b]],对于某些 b),或者它们不是。

例如如果你尝试构造一个混合列表,你会从编译器中得到一个错误:

Prelude> ["hello", ["world!"]]

<interactive>:3:12:
    Couldn't match expected type `Char' with actual type `[Char]'
    In the expression: "world!"
    In the expression: ["world!"]
    In the expression: ["hello", ["world!"]]

【讨论】:

  • 这不是关于混合列表,而是关于 [a] 和 [[a]]
  • @sindikat:但这些是不同的类型。第一个是一个列表,其元素的类型为a。第二个是一个列表,其元素的类型为[a]。因此,回答您的问题有意义的函数必须是一个列表其中元素是不同类型的(具体是a[a])。这样的列表是异构的,而 Haskell 列表不是。
【解决方案2】:

函数类型[a] -&gt; Bool 隐含地表示forall a. [a] -&gt; Bool,换句话说,它对所有可能元素类型的列表定义相同。这确实包括[[Int]][[[String]]] 之类的类型或您能想到的任何嵌套深度。但是,元素类型是什么对你的函数没有影响。

就这个函数而言,输入总是一个列表,其元素是一些不透明的、未知的类型。它永远不会收到包含相同不透明类型的嵌套列表。

【讨论】:

    【解决方案3】:

    在 Haskell 中,您不能真正以与在 Scheme 中相同的方式来考虑嵌套列表,因为它们不是相同的数据结构。 Haskell 列表是同质的,而 Lisp 的“列表”实际上更接近于玫瑰树(如下面的 C.A.McCann 所指出的)。作为一个说明性示例,看看WYAS48 parsing section 是如何定义LispVal 的。

    如果你真的,真的真的想要进行运行时类型检查,尽管这在 Haskell 中通常是一个坏主意并且非常不合常规,请查看 Data.TypeableThis response 也可能有用。

    这个问题的真正答案是“你需要在 Haskell 和 Lisp 中以不同的方式思考你的论点,这导致永远不需要在运行时自己执行这个检查”(我说这是一个 Common Lisper,所以我了解一开始是多么令人沮丧)。

    附录:为了响应您的编辑,Haskell 的类型系统会自动确保这一点。例如,如果你有一个foo :: [Int] -&gt; Int 类型的函数,并且你将它传递给["One", "Two", "Three"][[1, 2, 3]],你会得到一个编译时错误,告诉你刚刚发生了什么以及为什么发生了爆炸。如果你想专门化一个函数,只需声明一个更具体的类型。

    例如(不要写这样的代码,它只是为了说明目的),假设你有一个简单的函数,比如

    myLookup index map = lookup index map 
    

    如果你将它加载到 GHCi 并运行 :t myLookup,它会告诉你函数的类型是 myLookup :: Eq a =&gt; a -&gt; [(a, b)] -&gt; Maybe b,这意味着它可以采用派生 Eq 的任何类型的密钥(任何你可以运行的== 开)。现在,无论出于何种原因,您都希望确保使用数字作为键。您可以通过添加更具体的类型声明来确保

    myLookup :: Int -> [(Int, a)] -> Maybe a
    myLookup index map = lookup index map 
    

    现在,即使函数体中没有任何内容阻止它处理其他键类型,但如果您尝试传递除 Int 索引或其他内容之外的其他内容,则会在编译时收到类型错误除了[(Int, a)] 地图。结果,这

    myLookup :: Int -> [(Int, a)] -> Maybe a
    myLookup ix lst = lookup ix lst
    
    main :: IO ()
    main = putStrLn . show $ myLookup 1 [(1, "Foo")]
    

    会编译并运行良好,但是这个

    myLookup :: Int -> [(Int, a)] -> Maybe a
    myLookup ix lst = lookup ix lst
    
    main :: IO ()
    main = putStrLn . show $ myLookup "Nope.jpg" [("Foo", 1)]
    

    两者都不会。在我的机器上,它在编译时出错

    /home/inaimathi/test.hs:5:35:
        Couldn't match expected type `Int' with actual type `[Char]'
        In the first argument of `myLookup', namely `"Nope.jpg"'
        In the second argument of `($)', namely
          `myLookup "Nope.jpg" [("Foo", 1)]'
        In the expression:
          putStrLn . show $ myLookup "Nope.jpg" [("Foo", 1)]
    Failed, modules loaded: none.
    

    我真的希望这没有让你更加困惑。

    【讨论】:

    • 请注意,大多数情况下,当你真的,真的真的想要进行运行时类型检查时,你会弄错并且实际上并没有想要,因为这是一个不好的方法。即使在具有动态类型的语言中,显式类型检查通常也被认为是错误的形式。特别是,绝对不存在初学者应该与Typeable 混在一起的情况。
    • @C.A.McCann - 同意。来自 OP 的 sn-p 是 Lisps 中非常常见的递归情况(根据我所见,在 Erlang 中),您想检查下一个事物是原子还是另一个序列并相应地分支。除非您处理异构列表,否则您永远不需要这样做。
    • 在这种情况下是有意义的,因为 cons 单元传统上是 lisps 中的一种通用数据结构。在 Haskell 中,您可以使用 a rose tree 来表达这种结构。
    【解决方案4】:

    好吧,我猜,在 Haskell 中,您只能使用 http://ideone.com/sPhRCP:

    main = do   -- your code goes here
        print $isFlat [Node 1, Node 2, Node 3]
        print $isFlat [Node 1, Node 2, Branch [Node 3, Node 4, Node 5], Node 6]
    
    data Tree a = Node a | Branch [Tree a]
    
    isFlat :: [Tree a] -> Bool
    isFlat = all isNode where
                          isNode (Node _) = True
                          isNode _ = False
    

    在非严格类型语言中,每个对象都有运行时类型信息,因此可以是多态的。这可能是一个复杂的强制网络,例如在 Scala 中(如果您使用 C++ 则不太复杂),或者只是“一切都是对象,对象就是一切”,就像在纯动态语言(Lisp、JS、...) .
    Haskell 是严格类型的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-01-10
      • 2012-01-10
      • 2021-12-11
      • 1970-01-01
      • 1970-01-01
      • 2016-09-05
      • 1970-01-01
      • 2018-05-23
      相关资源
      最近更新 更多