Cleanup Stack impl
Originally I defined `Stack` as an interface to ensure both Node and Empty variants would have an identical API. But exporting an interface allows a developer to define other implementations, when we really want to ensure that a Stack is precisely a Node or Empty instance. This PR changes to exporting a union of `Stack = Node | Empty`, and makes the interface private to the module.
Joe Savona committed
Apr 21, 2023 at 08:03 UTC
cb72726e384ad0ac5db22d51d8a76d5ee121917d
1 file changed
+20
-6
compiler/forget/src/Utils/Stack.ts
+20
-6
@@ -5,10 +5,24 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-export interface Stack<T> {
9
- push(value: T): Stack<T>;
8
+/**
9
+ * An immutable stack data structure supporting O(1) push/pop operations.
10
+ */
11
+export type Stack<T> = Node<T> | Empty<T>;
12
+
13
+// Static assertion that Stack<T> is a StackInterface<T>
14
+function _assertStackInterface<T>(stack: Stack<T>): void {
15
+ let _: StackInterface<T> = stack;
16
+}
17
+
18
+// Internal interface to enforce consistent behavior btw Node/Empty variants
19
+// Note that we export a union rather than the interface so that it is impossible
20
+// to create additional variants: a Stack should always be exactly a Node or Empty
21
+// instance.
22
+interface StackInterface<T> {
23
+ push(value: T): StackInterface<T>;
24
11
- pop(): Stack<T>;
25
+ pop(): StackInterface<T>;
26
27
contains(value: T): boolean;
28
@@ -25,7 +39,7 @@ export function empty<T>(): Stack<T> {
39
return EMPTY as any;
40
}
41
28
-class Node<T> implements Stack<T> {
42
+class Node<T> implements StackInterface<T> {
43
#value: T;
44
#next: Stack<T>;
45
@@ -58,9 +72,9 @@ class Node<T> implements Stack<T> {
72
}
73
}
74
61
-class Empty<T> implements Stack<T> {
75
+class Empty<T> implements StackInterface<T> {
76
push(value: T): Stack<T> {
63
- return new Node(value, this);
77
+ return new Node(value as T, this as Stack<T>);
78
}
79
pop(): Stack<T> {
80
return this;