Skip to content

Instantly share code, notes, and snippets.

@drslump
Created November 24, 2014 10:03
Show Gist options
  • Save drslump/05899c5bda1e738ec5cf to your computer and use it in GitHub Desktop.
Save drslump/05899c5bda1e738ec5cf to your computer and use it in GitHub Desktop.
typing-ideas.mjs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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