假设我正在尝试按以下方式在F#中构建一个简单的Stack:
type Stack =
| Empty
| Stack of String list
(我知道我可以递归定义它,但是对于这个示例,假设我想在其中有一个列表)
然后我定义如下的推送操作:
let push item deck =
match deck with
| Empty -> Stack [item]
| Stack d -> Stack (item::d)
但是当我执行弹出操作时...我没有成功。我想做这样的事情:
let pop (Stack d) =
match d with
| h::[] -> h,Empty
| h::t -> h,(Stack t)
现在,让我们也试着忽略一个事实,即我可能想要一个窥视/弹出操作对而不是返回一个元组。我想尝试的是编写一个弹出操作,该操作只接受一开始就不为空的Stack。
换句话说,我只希望此功能接受歧视联盟的一种情况。但是,我立即得到警告:“此表达式上的模式不匹配。例如,值'Empty'可能表示模式未涵盖的情况。'
按预期(警告之后),以下代码:
let empty = Empty
let s,st = pop empty
...编译并在运行时失败。我希望它在编译时失败。
我知道我可以为此使用其他选项,
let pop stack =
match stack with
| Empty -> None, Empty
| Stack (h::[]) -> Some h,Empty
| Stack (h::t) -> Some h,(Stack t)
或:
let pop stack =
match stack with
| Empty -> Error "Empty Stack"
| Stack (h::[]) -> Ok (h,Empty)
| Stack (h::t) -> Ok (h,(Stack t))
(在这两种情况下,我甚至可能根本不需要Empty例...)
但是我试图做一些更严格的限制。所以...我在这里想念什么?有没有办法实现我的尝试?想要它甚至有意义吗?
答案 0 :(得分:3)
仔细考虑您要实现的目标。考虑以下功能:
let foo (s: Stack) = pop s
foo
应该编译还是应该拒绝?考虑一下。
我在这里假设您已经考虑过,并提供了唯一合理的答案:您寻求的“限制”现在也应适用于foo
。也就是说,调用foo
的任何人还必须还仅提供非空堆栈。
好的,很公平。但是,让我们一直走到乌龟:
let main argv =
printf "Provide directive: "
let s =
match Console.ReadLine() with
| "empty" -> Empty
| _ -> Stack [1; 2; 3]
let p = pop s
0
现在该程序应该编译还是被拒绝?显然,这取决于用户的输入。 程序的类型取决于运行时值。这实际上是一个活跃的研究领域,并且有一个名称:此类程序称为 Dependently Typed 。简而言之,是值(可以)携带类型的时候,但是与.NET RTTI不同,编译器可以查看这些类型并可以证明有关它们之间关系的信息。令人着迷的东西。
无论好坏,F#不支持依赖类型,而且可能永远不会。
现在,我将假设您没有依赖类型是可以的,并且只想对已知(在编译时)知道您的“限制”堆栈不为空。
如果是这种情况,那么将DU分为两部分就可以轻松解决您的问题:
type NonEmptyStack<'a> = NonEmptyStack of top: 'a * rest: 'a list
type Stack<'a> = Empty | NonEmpty of NonEmptyStack<'a>
let push item stack =
match stack with
| Empty -> NonEmptyStack (item, [])
| NonEmpty (NonEmptyStack (top, rest)) -> NonEmptyStack (item, top::rest)
let pop (NonEmptyStack (top,rest)) =
match rest with
| i::tail -> (top, Stack (NonEmptyStack (i, tail)))
| _ -> (top, Empty)
请注意push
始终如何返回非空堆栈,而pop
仅接受非空堆栈。类型编码含义。这就是他们应该做的。
答案 1 :(得分:2)
问题出在您的模型上-有两种方法可以表示空堆栈:
let empty1 = Empty
let empty2 = Stack []
鉴于此,目前尚不清楚您希望它如何表现。这就是为什么我建议要么使用递归定义的堆栈,要么仅使用列表。这是您的选择:
// type alias
type Stack<'a> = 'a list
// single-case DU
type Stack<'a> = Stack of 'a list
// traditional recursive type (which happens to be exactly equivalent to list<'a>)
type Stack<'a> = Empty | Stack of 'a * Stack<'a>
也就是说,如果您真的只是想将其保留为现在的样子并进行编译,则只需匹配额外的“空”表示形式:
let pop stack =
match stack with
| Empty | Stack [] -> None, Empty
| Stack (h::[]) -> Some h,Empty
| Stack (h::t) -> Some h,(Stack t)