Created
November 24, 2014 10:03
-
-
Save drslump/05899c5bda1e738ec5cf to your computer and use it in GitHub Desktop.
typing-ideas.mjs
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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;; Implements *Parametric Polymorphism* (all types share the same implementation) | |
;; instead of *Adhoc Polymorphism* (implementation depends on the type). This plays | |
;; nice with JavaScript's runtime. | |
;; | |
;; *Single Dispatch*, in order to be close to the runtime, dispatching occurs on the | |
;; name ignoring any given arguments. | |
;; | |
;; Subtyping is provided via *Structural Typing*. Even if JS supports some form of | |
;; inheritance, for which a nominative system could work, it's not so commonly used | |
;; and the structural approach matches much better object augmentation patterns. | |
;; | |
;; *Algebraic Data Types* | |
;; - *Untagged union*/*Sum type* allow to better express the language dynamic runtime. | |
;; When hinting members it should warn when referencing items outside the intersection. | |
;; - *Product Type* is helpful for augmented objects and defining multiple | |
;; interfaces (T = T1 & T2) | |
;; | |
;; *Nullable type* (aka *Option type*) can signal optional properties and function | |
;; arguments. | |
;; | |
;; *Variadic functions* to support an arbitrary length arity | |
;; | |
;; Overall the type system is not designed to be *sound*, actually the main use case | |
;; for it is for tooling (ie: autocompletion). One of the drawbacks of not being a | |
;; strongly typed static language is that type inference will not be so powerful, | |
;; on the average program there will be many expressions requiring manual hinting | |
;; for the type system to be useful. | |
;; | |
;; Literature suggest that supporting subtyping with Hindley–Milner is hard because | |
;; of exploding inequality constrains, making it too slow in practice and producing | |
;; unreadable type errors. | |
;; So the best approach seems to be to go with *local* type inference, requiring | |
;; explicit typing for functions. It should be simply enough to implement and overall | |
;; global type inference can make code harder to understand anyway. | |
;; | |
;; Type inference resources: | |
;; - http://stackoverflow.com/a/6846972 | |
;; - http://sourceforge.net/p/advance-project/code/HEAD/tree/advance/src/eu/advance/logistics/flow/engine/inference/ | |
;; - http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.39.4979&rep=rep1&type=pdf | |
;; | |
;; *Feedback typing* (runtime type feedback) would probably be a nice concept to play | |
;; with, when the project is unit tested for instance, being able to generate typing | |
;; information from actually running the code could be a great way to automatically | |
;; support great code completion and find mismatching explicit type hints. | |
;; | |
TYPEREF ;; Number | |
TYPEVAR ;; a | |
FUNC ;; Type1 -> Type2 | |
RECORD ;; {prop: Type1} | |
LIST ;; [Type1] | |
TUPLE ;; [Type1, Type2] | |
SUM ;; Type1 | Type2 | |
PROD ;; Type1 & Type2 | |
MAYBE ;; Type? | |
MANY ;; *Type | |
DOC ;; "documentation for type" | |
;; Note: All types accept *especialization* for polymorphic variables but if they are not | |
;; available when resolved any variable is especialized as Object. | |
foo :: (Number, String) -> Number | |
;; FUNC([TYPEREF(Number), TYPEREF(String)], TYPEREF(Number)) | |
;; Sum type (assignable if matches any of the types) | |
foo :: Number | String | |
;; SUM(TYPEREF(Number), TYPEREF(String)) | |
foo :: | |
Number | |
String | |
;; SUM(TYPEREF(Number), TYPEREF(String)) | |
;; Product type (assignable if matches all the types) | |
foo :: Object & String | |
;; PROD(TYPEREF(Object), TYPEREF(String)) | |
foo :: [String] | |
;; LIST(TYPEREF(String)) | |
foo :: [String, Boolean, *String] | |
;; TUPLE(TYPEREF(String), TYPEREF(Boolean), MANY(TYPEREF(STRING))) | |
foo := (x, y) -> x * y | |
; Inferred as: (a, b) -> a * b | |
foo(10, 20) ; pass | |
foo('str', 20) ; fail (because * isn't defined for strings) | |
; '::' introduces a new type declaration | |
; ':=' introduces a new variable declaration (like Go) | |
foo :: (Number, Number?) -> Number | |
foo := (x, param=20) -> ... ; var foo = function (x, param) { param = ... } | |
bar :: String | |
bar := 'foo' | |
; for non locals the use is optional | |
this.foo := 'foo' | |
this.foo = 'foo' | |
foo(10, param=20) ; foo(10, {param: 20}) | |
foo(10, *[1,2,3]) ; foo(10, ...[1,2,3]) | |
foo = (param1, *params) ; foo = function(param1, ...params) | |
; Annotations work inside function parameters too | |
foo := (x :: Number) -> x * 2 | |
; Perhaps we can use single colon here | |
foo := (x: Number) -> x * 2 | |
; assign labels to arguments | |
foo :: (age: Number, name: String) -> Person | |
; perhaps we should use '::' to be similar to inline declarations | |
foo :: (age :: Number, name :: String) -> Person | |
; Parametric polymorphism | |
; lowercase names of upto two characters indicate type variables | |
foo :: a -> a | |
;; FUNC([TYPEVAR(a)], TYPEVAR(a)) | |
foo :: xs -> Number | |
;; FUNC([TYPEVAR(xs)], TYPEREF(Number)) | |
foo :: doc -> Number | |
;; FUNC([TYPEREF(doc)], TYPEREF(Number)) | |
; Constraining (here only types compatible with Element are accepted) | |
foo :: a = Element => a -> a | |
foo :: a[Element] -> a | |
;; FUNC([TYPEVAR(a, TYPEREF(Element))], TYPEVAR(a)) | |
; Operators. Kinda limited since we don't have a sound type system to | |
; transform based on the types or support on the runtime | |
+ :: (Number, Number) -> Number | |
+ :: (a, b) -> String | |
* :: a = Number => (a, a) -> a | |
/ :: a = Number => (a, a) -> a | |
> :: (a, b) -> Boolean | |
< :: (a, b) -> Boolean | |
== :: (a, b) -> Boolean | |
=== :: (a, b) -> Boolean | |
! :: a -> Boolean | |
; Tuples are defined using array quotation | |
swap :: (a, b) -> [b, a] | |
; or perhaps is better to use parens? | |
swap :: (a, b) -> (b, a) | |
;; FUNC([TYPEVAR(a), TYPEVAR(b)], TUPLE(TYPEVAR(b), TYPEVAR(a)) | |
; Union types | |
foo :: Number | String | |
;; SUM(TYPEREF(Number), TYPEREF(String)) | |
; Overloads by name | |
doit :: Number -> Number | |
doit :: String -> String | |
; Is equivalent to | |
doit :: (Number -> Number) | (String -> String) | |
; Upper case letters are aliases to stdlib types | |
; Note: This produces excessively terse declarations | |
; A : Array | |
; O : Object | |
; S : String | |
; N : Number | |
; B : Boolean | |
; R : RegExp | |
; D : Date | |
; U : undefined / void | |
; Polymorphic definition for an array | |
Array[a] :: { | |
; '*' means from 0 to N additional arguments | |
concat :: (Array[a], *Array[a]) -> Array[a] | |
every :: (a -> Boolean, Array[a]?) -> Boolean | |
filter :: (a -> Boolean, Array[a]?) -> [a] | |
forEach :: (a -> undefined, Array[a]?) -> undefined | |
indexOf :: (a, from: Number?) -> Number | |
join :: (sep: String?) -> String | |
;; FUNC([MAYBE(TYPEREF(String))], TYPEREF(String)) | |
lastIndexOf :: (a, from: ?Number) -> Number | |
length :: Number | |
map :: (a -> a, ?Array[a]) -> [a] | |
pop :: () -> a | |
push :: (a, *a) -> Number | |
;; FUNC([TYPEVAR(a), MANY(TYPEVAR(a))], TYPEREF(Number)) | |
reduce :: ( (prev: a, cur: a, index: Number, Array[a]?) -> undefined, initial: a ) -> undefined | |
reduceRight :: ( (prev: a, cur: a, index: Number, Array[a]?) -> undefined, initial: a ) -> undefined | |
reverse :: () -> Array[a] | |
;; FUNC([], TYPEREF(Array, TYPEVAR(a))) | |
shift :: () -> a | |
slice :: (begin:?Number, end:?Number) -> Array[a] | |
;; FUNC([MAYBE(TYPEREF(Number)), MAYBE(TYPEREF(Number))], TYPEREF(Array, TYPEVAR(a))) | |
every :: (a -> Boolean, ?Array[a]) -> Boolean | |
sort :: () -> undefined | |
sort :: ((a, a) -> Number) -> undefined | |
splice :: (idx: Number, cnt: Number, *a) -> Array[a] | |
unshift :: (a, *a) -> Number | |
} | |
;; RECORD( [TYPEVAR(a)], { | |
;; concat: FUNC([TYPEVAR(a)), MANY(TYPEREF(Array, TYPEVAR(a)))], TYPEREF(Array, TYPEVAR(a))) | |
;; every: FUNC([TYPEVAR(a), MAYBE(TYPEREF(Array, TYPEVAR(a)))], TYPEREF(Boolean)) | |
;; length: TYPEREF(Number) | |
;; pop: FUNC([], TYPEVAR(a)) | |
;; unshift: FUNC([TYPEVAR(a), MANY(TYPEVAR(a))], TYPEREF(Number)) | |
;; }) | |
new Array :: () -> Array[Object] | |
new Array :: (Number) -> Array[Object] | |
new Array :: (a, *a) -> Array[a] | |
new Array :: (Object, *Object) -> Array[Object] | |
Array.isArray :: (Object) -> Boolean | |
;; FUNC([TYPEREF(Object)], TYPEREF(Boolean)) | |
jqEvent :: { | |
currentTarget :: Element | |
data :: {} | |
delegateTarget :: Element | |
isDefaultPrevented :: () -> Boolean | |
isImmediatePropagationStopped :: () -> Boolean | |
isPropagationStopped :: () -> Boolean | |
metaKey :: Boolean | |
namespace :: String | |
; Declaring with a quoted string attaches a description | |
pageX :: "The mouse position relative to the left edge of the document." | |
pageX :: Number | |
;; SUM(DOC("..."), TYPEREF(Number)) | |
pageY :: | |
"The mouse position relative to the top edge of the document." | |
Number | |
preventDefault :: () -> undefined | |
relatedTarget :: Element | |
stopImmediatePropagation :: () -> undefined | |
stopPropagation :: () -> undefined | |
target :: Element | |
timeStamp :: Number | |
type :: String | |
which :: Number | |
} | |
jqElement :: Element | jQuery | |
jqEventHandler :: Boolean | (jqEvent, extra: *Object) -> Boolean | |
; Type definition | |
jQuery :: { | |
; Typing the `this` reference | |
; Labeling it as "this", not so easy to read | |
on :: (this: Element, String, jqEventHandler) -> jQuery | |
; Specify the type as target of a call | |
on :: Element (String, jqEventHandler) -> jQuery | |
on :: Element String -> jQuery | |
;; FUNC(TYPEREF(Element), [TYPEREF(String)], TYPEREF(jQuery)) | |
click :: jqEventHandler | |
;; TYPEREF(jqEventHandler) | |
add :: (selector: String | Element | jQuery) -> jQuery | |
add :: (selector: String | Element | jQuery, context: Element) -> jQuery | |
Event :: String -> jqEvent | |
new Event :: String -> jqEvent | |
} | |
; Besides a type it can also be used as a function (to disambiguate) | |
jQuery() :: () -> jQuery | |
jQuery() :: (selector: String, jqElement?) -> jQuery | |
; But we can simply use the same LHS as the type | |
jQuery :: jqElement -> jQuery | |
jQuery :: [jqElement] -> jQuery | |
jQuery :: {} -> jQuery | |
; Augmenting declarations | |
jQuery :: jQuery & { | |
height :: () -> Number | |
innerHeight :: """ | |
Get the current computed inner height (including padding but not border) for | |
the first element in the set of matched elements or set the inner height of | |
every matched element. """ | |
;; DOC('...') | |
innerHeight :: () -> Number | |
;; SUM(DOC('...'), FUNC([], TYPEREF(Number))) | |
} | |
jQueryAjaxSettings :: { | |
accepts :: {} | |
async :: Boolean | |
beforeSend :: (jqXHR, settings: Object) -> Boolean | |
cache :: Boolean | |
complete :: (jqXHR, status: String) -> Boolean | |
; Type for any value in an object (Key is supposed to be a string) | |
contents :: { #: RegExp } | |
contentType :: String | |
context :: Object | |
; Object where each value is a bool or a function | |
converters :: {Boolean | Function} | |
crossDomain :: Boolean | |
data :: String | Object | Array | |
dataFilter :: (data: String, type: String) -> Object | |
error :: (jqXHR, status: String, error: String) -> Boolean | |
global :: Boolean | |
headers :: Object[String] ;; {String} | |
ifModified :: Boolean | |
isLocal :: Boolean | |
jsonp :: String | |
jsonpCallback :: String | Function | |
mimeType :: String | |
password :: String | |
processData :: Boolean | |
scriptCharset :: String | |
; Force a type for keys | |
statusCode :: Object[Number, Function] | |
success :: (data: Object, status: String, jqXHR) -> Boolean | |
timeout :: Number | |
traditional :: Boolean | |
type :: String | |
url :: String | |
username :: String | |
xhr :: XMLHttpRequest | |
xhrFields :: Object | |
} | |
; {} is a shorthand for {Object} | |
new jQuery :: (Function | {}) -> jQuery | |
jQuery.ajax :: (String, jQueryAjaxSettings?) -> jQuery | |
jQuery.ajax :: jQueryAjaxSetttings -> jQuery | |
XMLHttpRequest :: { | |
open :: (method: String, url: String) -> undefined | |
open :: (method: String, url: String, async: Boolean) -> undefined | |
open :: (method: String, url: String, async: Boolean, user: String, passwd: String) -> undefined | |
; Hint that it has optional arguments which might not be present | |
open :: (method: String, url: String, async: Boolean?, user: String?, passwd: String?) -> undefined | |
setRequestHeader :: (header: String, value: String) -> undefined | |
; Hint that it's optional and can be null or undefined | |
onreadystatechange :: Function? | |
readyState :: Number | |
response :: ArrayBuffer | Blob | Document | Object | String | |
; Hint that it's read only | |
get responseText :: String | |
responseType :: String | |
get responseXML :: Document? | |
status :: Number | |
get statusText :: String | |
timeout :: Number | |
ontimeout :: Function | |
upload :: XMLHttpRequestUpload | |
withCredentials :: Boolean | |
} | |
; Define the signature for the constructor | |
new XMLHttpRequest :: (settings: {}) -> XMLHttpRequest | |
new XMLHttpRequest :: () -> XMLHttpRequest | |
jqXHRok :: (data: Object, status: String, jqXHR) -> undefined | |
jqXHRko :: (jqXHR, status: String, error: String) -> undefined | |
jqXHR :: XMLHttpRequest & { | |
getResponseHeader :: () -> String | |
overrideMimeType :: String -> undefined | |
done :: jqXHRok -> undefined | |
fail :: jqXHRko -> undefined | |
always :: | |
jqXHRok -> undefined | |
jqXHRko -> undefined | |
then :: jqXHRok -> undefined | |
then :: (jqXHRok, jqXHRko) -> undefined | |
} | |
; Define an alias for an untyped variable | |
$ :: jQuery := jQuery | |
$('.foo').height() | |
; Define the type members (i.e. the prototype) | |
Date :: { | |
getDate :: () -> Number | |
getDay :: () -> Number | |
getMilliseconds :: () -> Number | |
setTime :: Number -> undefined | |
} | |
; Define the constructors | |
new Date :: () -> Date | |
new Date :: (Number) -> Date | |
new Date :: (formatted: String) -> Date | |
new Date :: (Y: Number, M: Number, D: Number?, h: Number?, m: Number?, s: Number?, ms: Number?) -> Date | |
;; Use a tuple to define variants | |
new Date :: | |
"Instantiate a new Date type" | |
() -> Date | |
(Number) -> Date | |
(formatted: String) -> Date | |
(Y:Number, M:Number, D:?Number, h:?Number, m:?Number, s:?Number, ms:?Number) -> Date | |
; Define "static" members | |
Date.UTC :: (Y: Number, M: Number, D: Number?, h: Number?, m: Number?, s: Number?, ms: Number?) -> Number | |
Date.now :: "Obtain current milliseconds from Unix epoch" | |
Date.now :: () -> Number | |
Date.parse :: String -> Number | |
;; Tuple gets converted to union | |
Date.now :: | |
"Obtain current milliseconds from Unix epoch" | |
() -> Number | |
;; SUM(DOC("..."), FUNC([], TYPEREF(Number))) | |
; Define implementation with inline type hints | |
Foo := { | |
;on :: Foo String -> jQuery | |
on: (s :: String) :: jQuery -> | |
s + 'foo' | |
} | |
;; RECORD({ | |
;; on: FUNC([TYPEREF(String)], TYPEREF(jQuery)) | |
;; }) | |
PlainObject[a] :: { | |
[String] :: a | |
} | |
Map[a, b] :: { | |
[a] :: b | |
get size :: Number | |
clear :: () -> undefined | |
delete :: (a) -> Boolean | |
get :: (a) -> b | |
} | |
WeakMap[a, b] :: { | |
[a] :: b | |
clear :: () -> undefined | |
delete :: (a) -> Boolean | |
get :: (a) -> b | |
} | |
Set[a] :: { | |
add :: (a) -> undefined | |
has :: (a) -> Boolean | |
} | |
WeakSet[a] :: { | |
add :: (a) -> undefined | |
has :: (a) -> undefined | |
} | |
;; Augments a polymorphic type by specializing it with a variable | |
MySet[a] :: Set[a] & { | |
first :: a | |
} | |
foo :: (Number, Map) -> String | |
foo :: (a:Number, b:String) => (Number, Map[a, b]) -> b | |
foo :: (Number, Map[Number, String]) -> String | |
ArrayBuffer :: { | |
get byteLength :: Number | |
slice :: (Number, end:Number) -> ArrayBuffer | |
} | |
new ArrayBuffer :: (Number) -> ArrayBuffer | |
ArrayBuffer.isView :: (Object) -> Boolean | |
;; Define a polymorphic "indexed" object | |
TypedArray[a] :: { | |
[Number] :: a | |
;; DOUBT: Define constructor inside a record | |
;; Since in JS constructors are not "inherited" I'm not sure | |
;; it's a good idea | |
new :: (len:Number) -> TypedArray[a] | |
new :: (TypedArray) -> TypedArray[a] | |
new :: (Object) -> TypedArray[a] | |
new :: (ArrayBuffer, ofs:Number?, len:Number?) -> TypedArray[a] | |
get buffer :: ArrayBuffer | |
get byteLength :: Number | |
get byteOffset :: Number | |
get length :: Number | |
copyWithin :: (target:Number, start:Number, end:Number?) | |
set :: (Array, offset:Number?) | |
set :: (TypedArray[a], offset: ?Number) | |
subarray :: (begin :? Number, end :? Number) -> TypedArray[a] | |
} | |
;; DOUBT: Define constructors outside records | |
;; Allows to define specific constructors as functions with a modifier | |
new TypedArray :: (length:Number) -> TypedArray[Object] | |
new TypedArray :: (TypedArray[a]) -> TypedArray[a] | |
new TypedArray :: (Object) -> TypedArray[Object] | |
new TypedArray :: (ArrayBuffer, ofs:Number?, len:Number?) -> TypedArray[Object] | |
;; Specialize a polymorphic object | |
Int8Array :: TypedArray[Number] | |
Int16Array :: TypedArray[Number] | |
Int32Array :: TypedArray[Number] | |
Uint8Array :: TypedArray[Number] | |
Uint16Array :: TypedArray[Number] | |
Uint32Array :: TypedArray[Number] | |
Float32Array :: TypedArray[Number] | |
Float64Array :: TypedArray[Number] | |
numbers :: Array[Number] | |
numbers := new Array(10) | |
numbers := [10] ;; Array[Number] | |
numbers := [10 :: Object] ;; Array[Object] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment