我有自定义列表类型:
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%正确。
答案 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)
theBar
和null
在某种意义上是“平行的”,或更确切地说,它们具有相同类型 - 您可以在程序中替换另一个它会在两种情况下编译。
另一方面,在Haskell中,字符串"hello"
和Nothing
不具有相同的类型,并且您不能使用另一个的字符串。 Haskell区分了这三件事:
"hello" :: String
Nothing :: Maybe String
Just "hello" :: Maybe String
#1和#3之间的区别是你在函数中系统地缺少的。使用Maybe a
时,如果您确实拥有某个值,则必须使用Just
,这就像一个包装器,表示“这不仅仅是{{1} },这是一个a
。“
您遗失的第一个地方Maybe a
是Just
表达式的右侧,我们可以这样解决:
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
所以你可能会认为“尾巴”就是其他一切。好吧,问题是“其他一切”不是xs
或cListHead :: 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))
中定义的结构没有子部分包含1
和example
,但不包含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
那样以通用的方式定义,你需要自定义函数,就像你一样。
另见:
Alternative
。答案 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)