在Haskell中获取自定义列表类型的头部和尾部

时间:2013-03-27 04:11:04

标签: list haskell functional-programming

我有自定义列表类型:

data NNList a = Sing a | Append ( NNList a) ( NNList a) deriving (Eq)
data CList a = Nil | NotNil ( NNList a) deriving (Eq)

我正在尝试实现一个返回列表头部和尾部的函数:

  

cListGet :: CList a - >也许(a,CList a)

我的尝试:

cListGet :: CList a -> Maybe (a, CList a)
cListGet Nil             = Nothing
cListGet xs@(NotNil nxs) =
case nxs of
  Sing x        -> (x, Nil)
  Append l r    -> ((fst $ cListGet (NotNil l)), (Append (snd $ cListGet (NotNil l)), r))

对我来说意味着继续向左走,直到我得到一个。一旦我得到单个元素(head),返回元素和Nil列表。然后将该Nil列表与列表组合,然后将其作为最终结果返回。

我甚至不确定逻辑是否100%正确。

4 个答案:

答案 0 :(得分:13)

嗯,人们通常会将您拥有的数据结构称为一种树,而不是列表。但无论如何......

问题#1:Haskell是缩进敏感的,并且您的case表达式没有缩进。这导致解析错误。

问题#2,更大的问题:你还没有理解Maybe类型的工作原理。我觉得你认为它在更常见的语言中就像空值一样,这会让你失望。

在像Java这样的语言中,null是一个值,可以出现在大多数其他值都可以的地方。如果我们有一个带有以下签名的方法:

public Foo makeAFoo(Bar someBar)

...然后通过以下方式之一调用它是合法的:

// Way #1: pass in an actual value
Bar theBar = getMeABar();
Foo result = makeAFoo(theBar);

// Way #2: pass in a null
Foo result2 = makeAFoo(null)

theBarnull在某种意义上是“平行的”,或更确切地说,它们具有相同类型 - 您可以在程序中替换另一个它会在两种情况下编译。

另一方面,在Haskell中,字符串"hello"Nothing 具有相同的类型,并且您不能使用另一个的字符串。 Haskell区分了这三件事:

  1. 需要在那里的字符串:"hello" :: String
  2. 缺少可选字符串:Nothing :: Maybe String
  3. 存在可选字符串:Just "hello" :: Maybe String
  4. #1和#3之间的区别是你在函数中系统地缺少的。使用Maybe a时,如果您确实拥有某个值,则必须使用Just,这就像一个包装器,表示“这不仅仅是{{1} },这是一个a。“

    您遗失的第一个地方Maybe aJust表达式的右侧,我们可以这样解决:

    case

    但这不是它的结束,因为你正在做-- This still fails to compile! cListGet :: CList a -> Maybe (a, CList a) cListGet Nil = Nothing cListGet xs@(NotNil nxs) = case nxs of -- I added 'Just' here and in the next line: Sing x -> Just (x, Nil) Append l r -> Just (fst $ cListGet (NotNil l), (Append (snd $ cListGet (NotNil l)), r)) ,它会遇到相反的问题:fst $ cListGet (NotNil l)返回cListGet,但是Maybe (a, CList a)有效在fst上,而不在(a, b)上。您需要对Maybe (a, b)的结果进行模式匹配,以测试它是cListGet还是Nothing。 (Just (x, l')也会出现同样的问题。)

    第三,您使用的snd $ cListGet (NotNil l)构造函数错误。您的格式为Append(Append foo, bar)foo之间不应使用逗号。在Haskell中,这种事情会比大多数其他语言给你更多令人困惑的错误消息,因为当Haskell看到这一点时,它并没有告诉你“你犯了语法错误”; Haskell比大多数语言更加文字化,因此它表示你试图与bar作为第一个元素,而Append foo作为第二个元素,因此得出结论{{1}必须有bar类型。

    第四个也是最后一个问题:你自己设定的问题没有明确说明,因此没有好的答案。你说你想要找到(Append foo, bar)的“头部”和“尾部”。那是什么意思?对于Haskell (NNList a -> NNList a, NNList a)类型,带有构造函数CList a[a],这很清楚:头部是[]中的:,尾部是是x

    据我所知,“head”的意思似乎是递归结构中最左边的元素。我们可以这样做:

    x:xs

    所以你可能会认为“尾巴”就是其他一切。好吧,问题是“其他一切”不是xscListHead :: CList a -> Maybe a cListHead Nil = Nothing -- No need to cram everything together into one definition; deal with -- the NNList case in an auxiliary function, it's easier... cListGet (NotNil nxs) = Just (nnListHead nxs) -- Note how much easier this function is to write, because since 'NNList' -- doesn't have a 'Nil' case, there's no need to mess around with 'Maybe' -- here. Basically, by splitting the problem into two functions, only -- 'cListHead' needs to care about 'Maybe' and 'Just'. nnListHead :: NNList a -> a nnListHead (Sing a) = a nnListHead (Append l _) = nnListHead l 子部分。举个例子:

    CList

    “头部”是NNList。但example :: CList Int example = NotNil (Append (Append (Sing 1) (Sing 2)) (Sing 3)) 中定义的结构没有子部分包含1example,但不包含2。你必须构造一个与原始形状不同的新3来获得它。这是可能的,但坦率地说,我认为它不是初学者练习的价值。

    如果我不清楚“子部分”的含义,请将该示例视为树:

    1

    Subpart =子树。

答案 1 :(得分:3)

提示:尝试仅使用模式匹配而不是相等检查(==)重写此内容。

修改

首先,了解模式匹配是什么以及如何工作至关重要。我建议你去here并阅读;网上还有很多其他资源(谷歌是你的朋友)。

完成后,这是另一个提示:首先编写一个函数nnListGet :: NNList a -> (a, CList a),然后用它来实现cListGet

答案 2 :(得分:1)

只是为了添加其他(非常彻底的)答案:很高兴认识到您的自定义列表是可折叠的结构。这意味着,它代表了一系列可以组合在一起的值。此类数据类型可以实现Foldable类型类。在你的情况下,它将是:

import Prelude hiding (foldr)
import Data.Foldable

data NNList a = Sing a | Append (NNList a) (NNList a) deriving (Eq)
data CList a = Nil | NotNil (NNList a) deriving (Eq)

instance Foldable NNList where
    foldr f z (Sing x)          = f x z
    foldr f z (Append xs ys)    = foldr f (foldr f z ys) xs
instance Foldable CList where
    foldr _ z Nil               = z
    foldr f z (NotNil xs)       = foldr f z xs

由此您可以免费获得Data.Foldable中定义的所有功能,例如最大/最小,搜索元素等。

对于任何Foldable,您可以使用First monoid实现返回其第一个元素的headMaybe。这是一个非常简单的monoid,它返回最左边的非空元素。因此,如果您使用此幺半群折叠Foldable的所有元素,您将获得它的第一个:

import Data.Monoid

headMaybe :: (Foldable f) => f a -> Maybe a
headMaybe = getFirst . foldMap (First . Just)

(或者,您可以使用foldr的{​​{3}}实例直接使用Maybe,这又会返回最左边的非空元素:

import Control.Applicative

headMaybe = foldr (\x y -> pure x <|> y) Nothing

但是,这并不能解决问题的第二部分 - 计算tailMaybe。这不能像headMaybe那样以通用的方式定义,你需要自定义函数,就像你一样。

另见:

答案 3 :(得分:0)

为什么你用两种类型声明?这是一个看似更合适的类型声明,具有正确的功能:

data CList a
  = Nil
  | Sing a
  | Append (CList a) (CList a)
  deriving (Eq)

headAndTail :: CList a -> Maybe (a, CList a)
headAndTail Nil = Nothing
headAndTail (Sing a) = Just (a, Nil)
headAndTail (Append a b) = 
  case headAndTail a of
    Nothing -> headAndTail b
    Just (head, tail) -> Just (head, Append tail b)