Last active
December 9, 2022 21:52
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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