structure PersonDB = struct

    exception RecordNotFound

    (*
     * Record ia a parameterized type at the top of the class hierarchy.
     *)
    type 'x Record = {
        Name : string,
        Age : int,
        Address : string,
        SpecializedInfo: 'x
    }

    (*
     * A PersonRecord is subtype of Person with no specialization.
     *)
    type PersonRecord = {} Record

    (*
     * A StaffEmployee is a subtype of Record, with two added fields.
     *)
    type StaffEmployee = {
        HourlyWage: int,
        EmploymentStatus : int
    } Record

    (*
     * A SalariedEmpolyee is a subtype of Record, with two added fields.
     *)
    type 'x SalariedEmployee = {
        Salary: int,
        Step: int,
        SpecializedInfo: 'x
    } Record

    (*
     * A Programmer is a subtype of Salaried Employee (subsubtype of Person),
     * with one added field.
     *)
    type Programmer = {
        Languages: string list
    } SalariedEmployee

    (*
     * A Manager is a subtype of Salaried Employee (subsubtype of Person), with
     * one added field.
     *)
    type Manager = {
        Supervisees: string list
    } SalariedEmployee

    (*
     * A GenericRecord is the union of all record types that will be stored in
     * the database.  Whenever a new subclass is added to the Record hierarchy,
     * its type must be "registered" in the GenericRecord union.
     *)
    datatype GenericRecord =
        PR of PersonRecord |
        SE of StaffEmployee |
        SaE of {} SalariedEmployee |
        P of Programmer |
        M of Manager
    (*with*)
        exception BadGenericRecord
        fun GetPR(PR(x)) = x |
            GetPR(x) = raise BadGenericRecord
        fun GetSE(SE(x)) = x |
            GetSE(x) = raise BadGenericRecord
        fun GetSaE(SaE(x)) = x |
            GetSaE(x) = raise BadGenericRecord
        fun GetP(P(x)) = x |
            GetP(x) = raise BadGenericRecord
        fun GetM(M(x)) = x |
            GetM(x) = raise BadGenericRecord
    (*end*)

    (*
     * A KeyedRecord is a record with an unbundled key, so that a generic
     * database find operation can be implemented.  It's a datatype instead of
     * an abstype so that the find operation can access its constructors.
     *
     * We associate the constructors for each (sub)type of record with this
     * keyed record, since its what has all of the data packaged inside it.
     *)
    datatype KeyedRecord =
        EmptyRecord |
        Data of {Key: string, Data: GenericRecord}
    (*with*)
        fun NewPersonRecord(NameVal, AgeVal, AddressVal) =
            Data{Key=NameVal, Data=
                PR{Name=NameVal, Age=AgeVal, Address=AddressVal,
                    SpecializedInfo={}}}

        fun NewStaffEmployee(NameVal, AgeVal, AddressVal, HourlyVal, ESVal) =
            Data{Key=NameVal, Data=
                SE{Name=NameVal, Age=AgeVal, Address=AddressVal,
                    SpecializedInfo={HourlyWage=HourlyVal,
                        EmploymentStatus=ESVal}}};

        fun NewSalariedEmployee(NameVal, AgeVal, AddressVal,
                SalaryVal, StepVal) =
            Data{Key=NameVal, Data=
                SaE{Name=NameVal, Age=AgeVal, Address=AddressVal,
                    SpecializedInfo={Salary=SalaryVal, Step=StepVal,
                        SpecializedInfo={}}}}

        fun NewProgrammer(NameVal, AgeVal, AddressVal,
                SalaryVal, StepVal, LangsVal) =
            Data{Key=NameVal, Data=
                P{Name=NameVal, Age=AgeVal, Address=AddressVal,
                    SpecializedInfo={Salary=SalaryVal, Step=StepVal,
                        SpecializedInfo={Languages=LangsVal}}}}

        fun NewManager(NameVal, AgeVal, AddressVal, SalaryVal, StepVal,
                SuperviseesVal) =
            Data{Key=NameVal, Data=
                M{Name=NameVal, Age=AgeVal, Address=AddressVal,
                    SpecializedInfo={Salary=SalaryVal, Step=StepVal,
                        SpecializedInfo={Supervisees=SuperviseesVal}}}}

    (*end*)

    
    (*
     * PersonDatabase a is a generic database, intended to hold KeyedRecords.
     *)
    abstype 'p PersonDatabase = 
        EmptyDB |
        Body of 'p list
    with
        val NewPersonDatabase = EmptyDB

        fun AddPerson(EmptyDB, p) = Body([p]) |
            AddPerson(Body(b), p) = Body(b @ [p])

        fun UnBody(Body(b)) = b |
            UnBody(x) = nil

        fun DelPerson(EmptyDB, k) = EmptyDB |
            DelPerson(Body(Data((p as {Key=k,  ...})) :: ps), k1) =
                if k = k1 then
                    Body(ps)
                else
                    Body(Data((p:{Key: string, Data: GenericRecord})) ::
                        UnBody(DelPerson(Body(ps), k1))) |
            DelPerson(x) = EmptyDB

        fun FindPerson(EmptyDB, k) = raise RecordNotFound |
            FindPerson(Body(Data((p as {Key=k,  ...})) :: ps), k1) =
                if k = k1 then
                    #Data(p:{Key: string, Data: GenericRecord})
                else
                    FindPerson(Body(ps), k1) |
            FindPerson(x) = raise RecordNotFound
    end

end