Papuascript

A Function-oriented Dialect of JavaScript


Pages


This page is a work in progress

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