Skip to content

Instantly share code, notes, and snippets.

@Luna-Klatzer
Last active December 9, 2022 21:52
Show Gist options
  • Save Luna-Klatzer/7c920cf8564ac088a798d98fa3357af9 to your computer and use it in GitHub Desktop.
Save Luna-Klatzer/7c920cf8564ac088a798d98fa3357af9 to your computer and use it in GitHub Desktop.
Runtime-types proof of concept for Kipper. This shows the possible implementation of runtime types, where you will be able to check the types against constant type identifiers and will be able to perform checks using the `match` operator.
/**
* Sample Script for showing the core functionality of how runtime types should work in Kipper.
*/
"use strict";
// @ts-ignore
var __kipperGlobalScope = typeof __kipperGlobalScope !== "undefined" ? __kipperGlobalScope : typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
// @ts-ignore
var __kipper = __kipperGlobalScope.__kipper = __kipperGlobalScope.__kipper || __kipper || {};
// The parent of all Kipper runtime types
// Fields:
// - typeName: The name that is used to identify the type in the Kipper source code
// - __match: The match function that is used to check if a value is of this type
// - __constructor: The constructor function that is used to create a new instance of this type
// - __create: The function that accepts one argument and creates a new instance of this type based on the value.
// Uses the __constructor if it is defined.
__kipper.typeConstructor = Object.seal(class Type { typeName = "type"; __match = (__exp) => __exp && __exp instanceof __kipper.typeConstructor; __constructor = undefined; __create = () => this; });
__kipper.type = Object.seal(new __kipper.typeConstructor());
// The runtime types of all base types
__kipper.num = Object.seal(new class Num extends __kipper.typeConstructor { typeName = "num"; __match = (__exp) => typeof __exp === "number"; __constructor = Number; __create = (__exp) => __exp !== undefined ? Number(__exp) : 0; });
__kipper.str = Object.seal(new class Str extends __kipper.typeConstructor { typeName = "str"; __match = (__exp) => typeof __exp === "string"; __constructor = String; __create = (__exp) => __exp !== undefined ? String(__exp) : ""; });
__kipper.bool = Object.seal(new class Bool extends __kipper.typeConstructor { typeName = "bool"; __match = (__exp) => typeof __exp === "boolean"; __constructor = Boolean; __create = (__exp) => Boolean(__exp); });
__kipper.void = Object.seal(new class Void extends __kipper.typeConstructor { typeName = "void"; __match = (__exp) => __exp === undefined; __create = () => undefined; });
__kipper.null = Object.seal(new class Null extends __kipper.typeConstructor { typeName = "null"; __match = (__exp) => __exp === null; __create = () => null; });
__kipper.undef = Object.seal(new class Undef extends __kipper.typeConstructor { typeName = "undef"; __match = (__exp) => __exp === undefined; __create = () => undefined; });
__kipper.obj = Object.seal(new class Obj extends __kipper.typeConstructor { typeName = "obj"; __match = (__exp) => __exp === undefined; __constructor = Object; __create = (__exp) => new Object(__exp); });
// A simple 'typeof()' function which shows a basic example of how the runtime types could work
__kipper.typeof = (__exp) => {
switch (typeof __exp) {
case "number": return __kipper.num;
case "string": return __kipper.str;
case "boolean": return __kipper.bool;
case "undefined": return __kipper.undef;
default: {
// For now, we are ignoring Symbol, Function and BigInt -> Will be included in the actual implementation
if (__exp === null) return __kipper.null
else if (__exp instanceof __kipper.typeConstructor) return __kipper.type
else return __kipper.obj
}
}
};
// All runtime types inherit from the base type constructor '__kipper.typeConstructor'
console.log(__kipper.num instanceof __kipper.typeConstructor); // true
// To that, when using the match function it will also return true for runtime types
console.log(__kipper.type.__match(__kipper.num)); // true
// The types also have their respective constructors defined, which may be used to create new objects from the type
// Depending on if new is used or not, this will return an object or a primitive value
console.log(new __kipper.num.__constructor(5));
console.log(__kipper.num.__constructor(5));
// If the type doesn't have a __constructor defined (e.g. the JavaScript constructor object), then undefined is returned
console.log(__kipper.undef.__constructor);
// In the Kipper source code, you will be able to do 'T(EXP)' to create a new instance of the type T from the value EXP
// This usually uses the __constructor if it is defined or returns a constant value, like for the type 'void' or 'null'
// (This is mostly just syntax-sugar to allow for a more concise syntax)
console.log(__kipper.num.__create("5"));
// Example typeof usage, where we compare a primitive value with a known type
console.log(__kipper.typeof(5) === __kipper.num);
// The advantage of this new typeof is now that we can access metadata from the type, like the name of the type or
// the match function, which can be used to check if a value is of a certain type
console.log(__kipper.typeof(5).typeName); // num
console.log(__kipper.typeof(5).__match(5)); // true
// The typeof will be also important for more advanced types to be able to check if a value is of a certain type, like
// checking whether an object matches a runtime interface
let exampleObj = {
a: 5,
b: "Hello World",
c: { },
};
let exampleInterfaceMatch = {
a: __kipper.num.__match,
b: __kipper.str.__match,
c: __kipper.bool.__match,
};
console.log(
__kipper.typeof(exampleObj).__match(__kipper.obj) && // first ensure it's an object
exampleInterfaceMatch.a(exampleObj.a) && // true
exampleInterfaceMatch.b(exampleObj.b) && // true
exampleInterfaceMatch.c(exampleObj.c) // false
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment