F#-在函数输入中限制有区别的联合字母大小写

时间:2018-08-11 00:02:38

标签: f# discriminated-union

假设我正在尝试按以下方式在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例...)

但是我试图做一些更严格的限制。所以...我在这里想念什么?有没有办法实现我的尝试?想要它甚至有意义吗?

2 个答案:

答案 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)