我需要为我正在处理的事情实现一般堆栈。该堆栈应该能够容纳不同类型的元素。例如,(1,'c',True,“Strings”)。要支持的功能是top,pop和push。
元组是最自然的想法。
push x s = (x,s)
pop s = snd s
top s = (fst s, s)
但我也需要支持空堆栈。这里,pop(和)没有在()上定义。 所以我尝试创建一个新类型。
data Stack = Empty | forall x. Cons (x, Stack)
push x s = Cons (x,s)
pop s = case s of
Empty -> Left s
Cons (x, y) -> Right y
top s = case s of
Empty -> (Left (), s)
Cons (x,y) -> (Right x, s)
在这里,top给了我一个错误:
Couldn't match expected type ‘b’ with actual type ‘x’
because type variable ‘x’ would escape its scope
This (rigid, skolem) type variable is bound by
a pattern with constructor
Cons :: forall x. (x, Stack) -> Stack,
in a case alternative
at try.hs:11:9-18
Relevant bindings include
x :: x (bound at try.hs:11:15)
top :: Stack -> (Either () b, Stack) (bound at try.hs:9:1)
In the first argument of ‘Right’, namely ‘x’
In the expression: Right x
如果我解决这个问题:
data Stack x = Empty | forall y. Cons (x, Stack y)
我得到了与pop相同的错误。
我也尝试添加这个:
type AnyStack = forall x. Stack x
但又一次得到了类似的错误:
Couldn't match expected type ‘b’ with actual type ‘Stack y’
because type variable ‘y’ would escape its scope
This (rigid, skolem) type variable is bound by
a pattern with constructor
Cons :: forall x y. (x, Stack y) -> Stack x,
in a case alternative
at try.hs:8:9-19
Relevant bindings include
y :: Stack y (bound at try.hs:8:18)
pop :: Stack t -> Either (Stack t) b (bound at try.hs:6:1)
In the first argument of ‘Right’, namely ‘y’
In the expression: Right y
有没有人可以帮我解决这种堆栈的正确类型签名或类型定义?或者可以指点一些与此相关的好参考?
非常感谢先进!
修改
如果我能够为此堆栈包含get函数,那将是完美的。给定一个整数i和一个堆栈s,get将返回s的第i个元素。我希望,一旦推出,弹出和顶部整理出来,我可能会自己做到这一点,但我仍然无法做到。关于这个人的任何想法?
答案 0 :(得分:1)
你应该不能在空元组上定义pop,但如果我们使用类型类来表示堆栈类型的情况,那么其余的就足够了。
class Stack h where
push :: a -> h x -> h (a, x)
pop :: h (a, x) -> (h x, a)
top :: h (a, x) -> (h (a, x), a)
top hax = let (_, a) = pop hax in (hax, a)
newtype S x = S x
instance Stack S where
push a (S x) = S (a, x)
pop (S (a, x)) = (S x, a)
如果你将push / pop / top和S抽象地与
一起公开sempty :: S ()
sempty = S ()
您可以确保没有人可以构建病态堆栈。如果你对GADT没问题,那么有更好的编码。
data S h where
Nil :: S ()
Cons :: a -> S x -> S (a, x)
您可以直接公开此GADT,因为它已经没有类型违规。
instance Stack S where
push = Cons
pop (Cons a x) = (x, a)
答案 1 :(得分:1)
你需要无类型吗?如果您愿意使用高级GHC功能,可以执行以下操作:
{-# LANGUAGE GADTs, DataKinds, KindSignatures, TypeOperators #-}
{-# LANGUAGE FlexibleInstances, FlexibleContexts #-}
module Stack (Stack(..), push, pop, top, empty) where
data Stack (h :: [*]) where
Empty :: Stack '[]
Push :: x -> Stack xs -> Stack (x ': xs)
instance Show (Stack '[]) where
showsPrec d Empty = showParen (d > 11) $ showString "Empty"
instance (Show x, Show (Stack xs)) => Show (Stack (x ': xs)) where
showsPrec d (Push x xs) = showParen (d > 10) $
showString "Push " . showsPrec 11 x . showChar ' ' . showsPrec 11 xs
instance Eq (Stack '[]) where
_ == _ = True
instance (Eq x, Eq (Stack xs)) => Eq (Stack (x ': xs)) where
(Push x xs) == (Push y ys) = x == y && xs == ys
instance Ord (Stack '[]) where
compare _ _ = EQ
instance (Ord x, Ord (Stack xs)) => Ord (Stack (x ': xs)) where
compare (Push x xs) (Push y ys) = case compare x y of
EQ -> compare xs ys
LT -> LT
GT -> GT
push :: x -> Stack xs -> Stack (x ': xs)
push = Push
pop :: Stack (x ': xs) -> Stack xs
pop (Push _ xs) = xs
top :: Stack (x ': xs) -> x
top (Push x _) = x
empty :: Stack '[]
empty = Empty
ghci中的一些用法如下所示:
[1 of 1] Compiling Stack ( typelist.hs, interpreted )
Ok, modules loaded: Stack.
*Stack> :t push True . push (Just 'X') . push 5 . push "nil" $ empty
push True . push (Just 'X') . push 5 . push "nil" $ empty
:: Num x => Stack '[Bool, Maybe Char, x, [Char]]
*Stack> push True . push (Just 'X') . push 5 . push "nil" $ empty
Push True (Push (Just 'X') (Push 5 (Push "nil" Empty)))
*Stack> pop . push True . push (Just 'X') . push 5 . push "nil" $ empty
Push (Just 'X') (Push 5 (Push "nil" Empty))
*Stack> pop empty
<interactive>:75:5:
Couldn't match type ‘'[]’ with ‘x0 : xs’
Expected type: Stack (x0 : xs)
Actual type: Stack '[]
Relevant bindings include
it :: Stack xs (bound at <interactive>:75:1)
In the first argument of ‘pop’, namely ‘empty’
In the expression: pop empty
请注意,此表示具有很好的功能,即在空堆栈上调用pop
或top
是编译时错误。但是,使用起来稍微困难一些,因为你总是需要证明你是用非空堆栈来调用它。它有利于防止错误,但有时需要更多的簿记来说服编译器你做对了。这种表现形式是一个不错的选择并不确定。这取决于用例。