Closed
Description
TypeScript version 2.5.3
I've been playing around with encoding existential types in TypeScript, which has become much easier due to improvements in type inference (thanks!) Here's an example:
type StackSig<S, A> = {
init(): S
push(s: S, x: A): void
pop(s: S): A
size(s: S): number
}
type Stack<A> = <R>(go: <S>(ops: StackSig<S, A>) => R) => R
const arrayStack = <A>(): Stack<A> =>
go => go<A[]>({
init: () => [],
push: (s, x) => s.push(x),
pop: s => {
if (s.length) return s.pop()!
else throw Error('empty stack!')
},
size: s => s.length,
})
const doStringStackStuff = (stack: Stack<string>) =>
stack(({ init, push, pop, size }) => {
const s = init()
push(s, 'hello')
push(s, 'world')
push(s, 'omg')
pop(s)
return size(s)
})
const result = doStringStackStuff(arrayStack())
expect(result).toBe(2)
This all works great; the type of the stack object s
is an opaque variable S
. However what happens if the type variable escapes the scope in which it's bound?
const s = arrayStack()(ops => ops.init())
s.bogus() // no type error, because `s: any`
What happens right now is that s
is quietly inferred to have type any
. It seems like it should be something like {}
or void
; a type with which we can't actually do anything meaningful.