pattern matching - tricíclicos - ¿Cómo se emulan los ADT y la coincidencia de patrones en TypeScript?
principales farmacos antidepresivos triciclicos (4)
Fui con el siguiente patrón similar al de Visitor, inspirado en esto y esto (en el ejemplo, una Choice
puede ser Foo
o Bar
):
interface Choice {
match<T>(cases: ChoiceCases<T>): T;
}
interface ChoiceCases<T> {
foo(foo: Foo): T;
bar(bar: Bar): T;
}
class Foo implements Choice {
match<T>(cases: ChoiceCases<T>): T {
return cases.foo(this);
}
}
class Bar implements Choice {
match<T>(cases: ChoiceCases<T>): T {
return cases.bar(this);
}
}
Uso:
function getName(choice: Choice): string {
return choice.match({
foo: foo => "Foo",
bar: bar => "Bar",
});
}
La coincidencia en sí misma es expresiva y segura, pero hay mucho que repetir para los tipos.
Desafortunadamente, a partir de la versión 0.9.5, TypeScript aún no (tiene) tipos de datos algebraicos (tipos de unión) y coincidencia de patrones (para desestructurarlos). Además, ni siquiera es compatible con instanceof en las interfaces. ¿Qué patrón usas para emular estas funciones del lenguaje con la máxima seguridad de tipo y el código mínimo repetitivo?
TypeScript 1.4 agrega tipos de unión y guarda de tipo .
Aquí hay una alternativa a la muy buena respuesta de @thSoft. En el lado positivo, esta alternativa
- tiene interoperabilidad potencial con objetos javascript sin formato en la forma
{ type : string } & T
, donde la forma deT
depende del valor deltype
, - tiene una repetición sustancialmente menor por elección;
en el lado negativo
- no hace cumplir estáticamente que coincida con todos los casos,
- no distingue entre diferentes ADT.
Se parece a esto:
// One-time boilerplate, used by all cases.
interface Maybe<T> { value : T }
interface Matcher<T> { (union : Union) : Maybe<T> }
interface Union { type : string }
class Case<T> {
name : string;
constructor(name: string) {
this.name = name;
}
_ = (data: T) => ( <Union>({ type : this.name, data : data }) )
$ =
<U>(f:(t:T) => U) => (union : Union) =>
union.type === this.name
? { value : f((<any>union).data) }
: null
}
function match<T>(union : Union, destructors : Matcher<T> [], t : T = null)
{
for (const destructor of destructors) {
const option = destructor(union);
if (option)
return option.value;
}
return t;
}
function any<T>(f:() => T) : Matcher<T> {
return x => ({ value : f() });
}
// Usage. Define cases.
const A = new Case<number>("A");
const B = new Case<string>("B");
// Construct values.
const a = A._(0);
const b = B._("foo");
// Destruct values.
function f(union : Union) {
match(union, [
A.$(x => console.log(`A : ${x}`))
, B.$(y => console.log(`B : ${y}`))
, any (() => console.log(`default case`))
])
}
f(a);
f(b);
f(<any>{});
Contestar
ni siquiera es compatible con instanceof en las interfaces.
La razón es borrado de tipo. Las interfaces son solo una construcción de tipo compilación y no tienen implicaciones de tiempo de ejecución. Sin embargo, puede usar instanceof en las clases, por ejemplo:
class Foo{}
var x = new Foo();
console.log(x instanceof Foo); // true