A Function-oriented Dialect of JavaScript
Functionality forms a Venn diagram, not a tree. Some data can be ordered, but not added (surnames can be ordered lexicographically, but adding them has no meaning); some data can be added, but not ordered (arrays containing mixed data can be concatenated, but ordering them would make no sense); and some data can be both added and ordered (numbers).
Here's a diagram of some types in Haskell:
Enum, Ord, Num, Integral, and Fractional are all
type classes. Data types
that are instances of Enum have a discrete set of possible values; Ord's can be
compared using <
and >
; Num's can be added,
subtracted and multiplied; Integral types supports mod
and
dividing with remainders; and Fractional types support division. If you can
work out a classical inheritance tree from this diagram, your kung fu is
better than mine.
OO languages have interfaces, but they require you to re-implement the methods of the interface for every class. Some OO languages have features that allow you to emulate the true Venn-diagram nature of data - Ruby has "modules," for example - but these tend to be incomplete or complicated or both, and, in any case, why base your language around inheritance in the first place? Overlapping, non-hierarchical relationships are the norm, not the exception.
Here is a simple emulation of Haskell-style type classes. Without Haskell's type system, our type classes will not have nearly the powerful they would have in that language, but they still provide a credible alternative to inheritance-based models.
typeClass defaultDefs typeClassFuncs methods =
/* function that adds type class functionality to a constructor
*
* constr$: type (constructor function) to be modified
* classFuncs: type-specific definitions and overrides
* for functions in this type class
*/
typeKlass = \constr$ typeFuncs ->
// set type functions by passing type to genDefault
for k:genDefault in defaultDefs
constr$[k] := typeFuncs[k] || genDefault constr$
// set methods by passing type's function to memberFunc
for k:genMethod in methods
constr$.prototype[k] := genMethod constr$[k]
// add generic functions to type class object
for kk:vv in typeClassFuncs
typeKlass[kk] = typeClassFuncs[kk]
typeKlass
As an example, we create the simple type class Show
, for
converting objects to strings.
// define type class Show
Show = typeClass
// generates default implementation for a given type
{ show: \type -> \a -> a.toString()
, showList: \type -> \xs -> fmap type.show xs .join ','
}
// these will become Show.show and Show.showList
{ show: \a -> a.show()
, showList: \xs -> xs.0.constructor.showList xs
}
// generates methods from type-level function
{ show: \f -> \ -> f this
}
And now we make RegExp an instance of Show; i.e., we add the functionality of Show to RegExp.
// make RegExp an instance of Show
Show RegExp
{ show: \regex -> regex.source
, showList: \xs -> '/' + fmap RegExp.show xs .join '/, /' + '/'
}
// now show and showList can be used with RegExp objects
console.log # /a*b*/.show()
console.log # Show.showList [/abc/, /def/]
Another example of a type class is Eq
, which defines equality.
Note that the default implementations of is
and isnt
call
each other. Types implementing Eq
must override at least one of
these defaults, or calling either will result in an infinite
loop!
// type class for things which can be equal or unequal
Eq = typeClass
{ is: \type -> \a b -> !(type.isnt a b)
, isnt: \type -> \a b -> !(type.is a b)
}
{ is: \a b -> a.is b
, isnt: \a b -> a.isnt b
}
{ is: \f -> f this @
, isnt: \f -> f this @
}
Eq RegExp
{ is: \a b -> a.source == b.source
}
console.log # /abc/ .is /abc/
console.log # /abc/ .isnt /def/
Project maintained by Rob Rosenbaum Theme by mattgraham, modified by Rob Rosenbaum