如何在 Haskell 中创建一定深度的列表

How to create a list of a certain depth in Haskell

他最近开始学习 Haskell。我有一个任务:用两个计算参数——数字 N 和原子 A 对函数 bubl N A 进行编程。该函数构建一个深度为 N 的列表;在最深层次上,列表的一个元素是 A,而在任何其他层次上,列表由一个元素组成。例如:bubl 3 5 => [[[5]]].

这是我解决这个问题的失败尝试:

bubl :: Integer -> a -> [a] -> [a]
bubl n b list
  | n == 0 = take 1 ([head list] : list)
  | n /= 0 = bubl (n - 1) b (take 1 [head list] : ---list)

我只是这方面的初学者,所以我需要你的帮助)

有一些高级方法可以解决此问题(见下文),但 Haskell 类型系统(设计)将阻止您为此任务编写直接的解决方案。

问题是 Haskell 函数通常无法根据输入参数更改其结果的类型,因此您不能在 Haskell 中编写函数定义,看起来像这个:

weird 1 = False            -- type is Bool
weird 2 = "Hello world!"   -- type is String
weird 3 = 42               -- type is Int, say

您尝试编写的 bubl 函数存在类似的问题,因为给定的列表类型具有固定的深度,如果深度取决于第一个参数,则结果的类型将不同取决于那个论点:

bubl 1 5 = [5]     -- type is [Int]
bubl 2 5 = [[5]]   -- type is [[Int]]
bubl 3 5 = [[[5]]] -- type is [[[Int]]]

最简单的解决方案(尽管对于初学者来说仍然有点高级)是定义一个新的 user-defined 类型,它可以表示不同深度的“列表”,但只使用一种类型。在实际程序中,这种类型的形式取决于使用 bubl 的上下文。在像这样的人工编程任务中,我们必须猜测。如果您希望能够表示可以任意嵌套到不同深度的整数“列表”,例如假设的“列表”:

[1,[1,2,3],[[4,[5],[[[[6]]]]]]]   -- note: not a valid Haskell list!

那么您可能会定义一个 Haskell 类型,例如:

data NestedList = Atom Int | Nest [NestedList]

或更一般地,对于具有潜在 non-integer 个原子的嵌套列表:

data NestedList a = Atom a | Nest [NestedList a]

上面的假设列表在使用这个新类型定义时看起来像这样:

ex1 = Nest [ Atom 1
           , Nest [Atom 1, Atom 2, Atom 3]
           , Nest [Nest [ Atom 4
                        , Nest [Atom 5]
                        , Nest [Nest [Nest [Nest [Atom 6]]]]
                        ]
                  ]
           ]

并且您可以定义一个使用此表示的 bubl 版本:

bubl :: Integer -> a -> NestedList a
bubl 0 b = Atom b
bubl n b = Nest [bubl (n-1) b]

如果您编写一个函数将 NestedList 转换为漂亮的字符串表示形式:

import Data.List (intercalate)

showNest :: (Show a) => NestedList a -> String
showNest (Atom a) = show a
showNest (Nest xs) = "[" ++ intercalate "," (map showNest xs) ++ "]"

您可以获得您希望的那种输出:

λ> putStrLn $ showNest (bubl 3 5)
[[[5]]]

还有其他解决方案与普通 Haskell 列表保持一致,但它们要高级得多。一个聪明的解决方案使用类型 类 让调用者确定列表的深度,从而无需将深度作为参数传递:

class Bubl a where
  bubl :: Int -> a
instance Bubl a => Bubl [a] where
  bubl x = [bubl x]
instance Bubl Int where
  bubl = id

main = do
  print (bubl 3 :: Int)
  print (bubl 3 :: [Int])
  print (bubl 3 :: [[[Int]]])

有了很多复杂的扩展,您可以调整此解决方案以采用 type-level 深度参数,这通过稍微修改语法(调用中的 @-sign)解决了原始任务。我包含这个例子只是为了表明它 技术上 可能具有许多高级 Haskell 功能。复杂性说明您真的不应该在“正常”Haskell 代码中执行此操作。

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

import GHC.TypeLits

class Bubl a where
  bubl :: Int -> a
instance Bubl a => Bubl [a] where
  bubl x = [bubl x]
instance Bubl Int where
  bubl = id

type family Nested n where
  Nested 0 = Int
  Nested n = [Nested (n-1)]

bubl' :: forall n. (Bubl (Nested n)) => Int -> Nested n
bubl' = bubl

main = do
  print $ bubl' @0 5   -- prints "5"
  print $ bubl' @3 5   -- prints "[[[5]]]"