I'm making good progress now, typeclassing the standard library.
Most of the tests now pass .. although some unchecked functions
may have been lost.

Here are some nasties.

1. to open typeclasses, you have to elaborate the instances:
************************************************************

open Eq[byte];
open Eq[address];
open Eq[caddress];
open Eq[vaddress];
open Eq[cvaddress];


open Signed_integer[tiny];
open Signed_integer[short];
open Signed_integer[int];
open Signed_integer[long];
open Signed_integer[vlong];
open Unsigned_integer[utiny];
open Unsigned_integer[ushort];
open Unsigned_integer[uint];
open Unsigned_integer[ulong];
open Unsigned_integer[uvlong];
open Real[float];
open Real[double];
open Real[ldouble];
open Addgrp[complex];
open Addgrp[dcomplex];
open Addgrp[lcomplex];
open Addgrp[imaginary];
open Addgrp[dimaginary];
open Addgrp[limaginary];

You cannot say

open Signed_integer[fast_sints];

where 

typedef fast_sints = typesetof(tiny,short,int,long,vlong);


This is partly because the correct syntax would have to be

open[t] Signed_integer[t:fast_sints];

in line with other such cases of constrained polymorphism.
There is another difficulty here: because lookup depends on
open modules and typeclasses, actually any lookup required
opening modules and typeclasses cannot so easily be done
in the full environment .. because that includes the symbols
that the open directive itself would make available, leading
to a difficult circularity.

Felix resolves this with two restricted scopes: a bare bones
scope and a semi-bare scope. A bare scope is one without any
open modules, the semi-bare scope is a full scope up to the
current one, but the current one is bare.

All of this is quite nasty to implement, and currently this
feature just isn't available. I did look at it, but I can't
see an easy way to implement it. I may have to come back to
this, see below.

2. to provide an instance of a class, you have to provide
   instances of all the inherited classes
**********************************************************

instance [t:basic_types] Eq[t] {
  fun eq: t * t -> bool = "$1==$2";
}

instance[t:reals] Tord[t] {
  fun lt: t * t -> bool = "$1<$2";
}

instance[t:numbers] Addgrp[t] {
  fun zero: 1 -> t = "(?1)0" ;
  fun add: t * t -> t = "$1+$2" ;
  fun neg: t -> t = "-$1" ;
  fun sub: t * t -> t = "$1-$2" ;
  proc pluseq: lvalue[t] * t = "$1+=$2;";
  proc minuseq: lvalue[t] * t = "$1-=$2;";
}

instance[t:numbers] MultSemi1[t] {
  fun one: unit -> t = "(?1)1";
  fun mul: t * t -> t = "$1*$2";
  proc muleq: lvalue[t] * t = "$1*=$2;";
}

instance[t:numbers] Ring[t] {}

instance[t:numbers] Dring[t] {
  fun div: t * t -> t = "$1/$2";
  fun mod: t * t -> t = "$1/$2";
  proc diveq: lvalue[t] * t = "$1%=$2;";
  proc modeq: lvalue[t] * t = "$1%=$2;";
}

instance [t:fast_uints] Bits [t] {
    fun bxor: t * t -> t = "$1^$2";
    fun bor: t * t -> t = "$1|$2";
    fun band: t * t -> t = "$1&$2";
    fun bnot: t -> t = "~$1";
    proc bxoreq: lvalue[t] * t = "$1^=$2;";
    proc boreq: lvalue[t] * t = "$1|=$2;";
    proc bandeq: lvalue[t] * t = "$1&=$2;";
}

instance[t:ints] Forward[t] {
  fun succ: t -> t = "$1+1";
  proc pre_incr: lvalue[t] = "++$1;";
  proc post_incr: lvalue[t] = "$1++;";
}

instance[t:ints] Bidirectional[t] {
  fun pred: t -> t = "$1-1";
  proc pre_decr: lvalue[t] = "--$1;";
  proc post_decr: lvalue[t] = "$1--;";
}

instance[t:ints] Integer[t] { }

instance[t:fast_sints] Signed_integer[t] {
  fun sgn: t -> int = "$1<0??-1:$1>0??1:0";
  fun abs: t -> t = "$1<0??-$1:$1";
}

instance[t:fast_uints] Unsigned_integer[t] {}

I am actually quite amazed that all this works. 
I'm not even sure why, even though I wrote the code :)

Typeclasses inheritance works like C++ virtual bases:
it creates an undiscriminated union of all the signatures.

The instances only provide incremental implementations.
Thus, to specify the functions for an integer types,
you cannot just provide an instantiation of Integer
with all the required signatures: you have to discretely
instantiate each 'slice' as shown above.

3. Instances have to be in global scope to be found
****************************************************

You cannot put instances in a module,
and expect them to become visible when you open the
module. Instances have to be in the same scope as
the typeclass they're instantiation -- this is the same
as the rule in C++ that specialisations must be in the
same namespace as the template they're specialising.

However namespaces are extensible, whilst modules
are not, so this can be quite inconvenient.

in the example below in (4) you can see some of the
pain -- the type being instantiated is 

Stl::vector::stl_vector_iterator[t]

and the name has to be written out in full.
You could use an abbreviation such as:

typedef vi[t] = Stl::vector::stl_vector_iterator[t];

but then that abbreviation would be public.

4. Typeclasses can only be parameterised by types
***************************************************

This is a severe restriction. To understand the problem
look at this:

//////////////////////////////////////////////////////////
  instance[t] Eq[Stl::Vector::stl_vector_iterator[t]] {
    fun eq: Stl::Vector::stl_vector_iterator[t] *
Stl::Vector::stl_vector_iterator[t] -> bool = "$1==$2";
  }
  instance[t] Tord[Stl::Vector::stl_vector_iterator[t]] {
    fun lt: Stl::Vector::stl_vector_iterator[t] *
Stl::Vector::stl_vector_iterator[t] -> bool = "$1<$2";
  }
  instance[t] Iterator[Stl::Vector::stl_vector_iterator[t],t] {
    fun deref : Stl::Vector::stl_vector_iterator[t] ->  lvalue[t]  = "*
$1";
  }
  instance[t] Forward[Stl::Vector::stl_vector_iterator[t]] {
    proc succ: Stl::Vector::stl_vector_iterator[t] = "$1+1;";
    proc pre_incr : lvalue[Stl::Vector::stl_vector_iterator[t]] =
"++$1;";
    proc post_incr : lvalue[Stl::Vector::stl_vector_iterator[t]] =
"++$1;";
  }
  instance[t] Bidirectional[Stl::Vector::stl_vector_iterator[t]] {
    proc pred: Stl::Vector::stl_vector_iterator[t] = "$1-11;";
    proc pre_decr : lvalue[Stl::Vector::stl_vector_iterator[t]] =
"--$1;";
    proc post_decr : lvalue[Stl::Vector::stl_vector_iterator[t]] =
"--$1;";
  }
  instance[t] Forward_iterator[Stl::Vector::stl_vector_iterator[t]] {}
  instance[t]
Bidirectional_iterator[Stl::Vector::stl_vector_iterator[t]] {}
  instance[t] Eq[Stl::Vector::stl_vector_reverse_iterator[t]] {
    fun eq: Stl::Vector::stl_vector_reverse_iterator[t] *
Stl::Vector::stl_vector_reverse_iterator[t] -> bool = "$1==$2";
  }

// REVERSE ITERATOR

  instance[t] Tord[Stl::Vector::stl_vector_reverse_iterator[t]] {
    fun lt: Stl::Vector::stl_vector_reverse_iterator[t] *
Stl::Vector::stl_vector_reverse_iterator[t] -> bool = "$1<$2";
  }
  instance[t] Iterator[Stl::Vector::stl_vector_reverse_iterator[t],t] {
    fun deref : Stl::Vector::stl_vector_reverse_iterator[t] ->
lvalue[t]  = "*$1";
  }
  instance[t] Forward[Stl::Vector::stl_vector_reverse_iterator[t]] {
    proc succ: Stl::Vector::stl_vector_reverse_iterator[t] = "$1+1;";
    proc pre_incr : lvalue[Stl::Vector::stl_vector_reverse_iterator[t]]
= "++$1;";
    proc post_incr : lvalue[Stl::Vector::stl_vector_reverse_iterator[t]]
= "++$1;";
  }
  instance[t] Bidirectional[Stl::Vector::stl_vector_reverse_iterator[t]]
{
    proc pred: Stl::Vector::stl_vector_reverse_iterator[t] = "$1-11;";
    proc pre_decr : lvalue[Stl::Vector::stl_vector_reverse_iterator[t]]
= "--$1;";
    proc post_decr : lvalue[Stl::Vector::stl_vector_reverse_iterator[t]]
= "--$1;";
  }
  instance[t]
Forward_iterator[Stl::Vector::stl_vector_reverse_iterator[t]] {}
  instance[t]
Bidirectional_iterator[Stl::Vector::stl_vector_reverse_iterator[t]] {}
/////////////////////////////////////

All of this code is required to instantiate the properties of
a the two bidirectional iterators over an STL vector, the
forward and reverse ones.

As explained in point 1, it is necessary to write out the
instances of each base typeclass to instantiate a typeclass.

Note that although instance[t] is universally quantified
over the data type t, so that the instances are data polymorphic,
that isn't all we wanted! What we REALLY want is for the instances
to be polyadic:

  instance[IT:TYPE->TYPE, t:TYPE ] 
  Eq[IT[t] where IT:bidirectional_iterators] 
  {
    fun eq: IT[t] * IT[t] -> bool = "$1==$2";
  }

Here, the kind of IT is 

        IT: TYPE -> TYPE

that is, IT is a type *constructor* or functor, and one of
those which is also a bidirectional iterator.

Haskell can do this. Felix can't at the moment: typeclass
parameters have to be kind

        TYPE

Lacking such functorial polymorphism .. parameterisation
of the data functors, not just types, we are forced to
elaborate each container separately.

Functorial polymorphism is the Holy Grail for programming languages.
I am not sure how easy it will be to implement this.
Felix does have parameterised types:

        typedef vector[t] = "std::vector<?1>";

and

        typedef fun vect(t:TYPE):TYPE => vector[t];

may be regarded as specifying 'vector' and/or 'vect' as being
data functor and/or type constructors. Nevertheless neither
can be manipulated as such:

        vect t

is simply substituted away, and vector[t] becomes a single
monomorphic type with an unknown argument: 'vector' cannot
be manipulated as an object in its own right.

Note this is the downfall of ML as well. Although it has
functors, they're not first class, they're just typesafe
macros which can be used to construct types given type
arguments.



-- 
John Skaller <skaller at users dot sf dot net>
Felix, successor to C++: http://felix.sf.net

-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Felix-language mailing list
Felix-language@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/felix-language

Reply via email to