To answer this, you need to first diagram carefully what is dependent on what. Since you are unsure how to mash these two cases together, FIRST write EACH one separately:

Case #1: computeBonus for a regular employee.

Actually sit down, and write code that does what you want for just this one situation (pretend you never heard of sales employees). Also write a test case that creates an employee with a given salary, and then passes that employee to the computeBonus function. Check that you get the expected result.

Case #2: computeBonus for a sales employee.

Now write this version by itself (pretend all employees are sales employees), complete with a test using actual numbers.

-------------------------------
NOW you can look at those two, and see where the commonality is. At that time, it is possible to design a meaningful structure.

I'll jump ahead and say that the key question will be: Does "computeBonus" for "salesEmployee" need access to the "commission" field?

If you don't see how to combine these two, then come back and post your two code snippets, and we'll discuss.

~TMSteve

By on 4/17/2008 1:12 AM ()

Thanks for replying to my post! Your comments were helpful.

I guess my question was more about how best to implement sub-typing of records in F#. F# doesn't seem to elegantly support this yet?

So, I feel that I have to resort to using the OO-features of F#, when all I want to do is have records be subtypes of eachother. Conceptually, what I want is this:

type Person = { Name: string ; Age: int}

type Employee \expands/extends/modifies Person\ >= {Name: string; Age : int; EmployeeID : int}

or maybe ... = {EmployeeID : int}

Then, I would like to have some nifty way of passing an Employee record into all the functions that already take a Person record. So, if any of those functions look like this:

let make11YearsOld (p:#Person) =

{p with Age = 11}

Then, it would be cool to have the function return either a Person or a Employee depending on what was passed in.

I don't want to use objects to to represent records. OO makes me type a gob of useless/repetitive code and stuff a bunch of method getters all over the place for no good reason at all. I hate all the interface stuff that distracts me from doing something really basic (creating a record).

I guess there are some articles on the F# Journal from Jon Harrop that seem to address this issue to some extent, but I am interested in any more comments you might have as well just to add more info to the topic.

I guess I *could* maybe go through all my code and replace references to the Person record with an Active Pattern called, say PersonSuperType. Then, my active pattern could match on either ... but that does not solve the problem of "returning" a record of either type, depending on what was passed in.

By on 4/17/2008 9:51 AM ()

Ah, now I understand why you referred to this as a copy constructor situation.

Unfortunately, records in F# are simply .NET structs; there is no way to attach additional data to them. Even the ability to augment with members doesn't help, since there is no place to store the additional data. See two approaches (dictionary to associate data, and wrapping data inside a class) in posts 5779 and 5780: [link:cs.hubfs.net]

However, those are both significant work. For contemplation by Don & F# team, I would describe an idealized "wished for" syntax as something like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
 
  // ----- Desired Feature: Subclassing of Records ------
  type Person = { Name: string; Age: int}
  type Employee extends Person with {EmployeeID : int}
  let make11YearsOld (p:#Person) =
    {p with Age = 11}

  // ----- Usage -----
  let p1 = {Name: "Fred"; Age: 2}
  let p2 = make11YearsOld p1
  let p3 = {Name: "Sam"; Age: 21; EmployeeID: 1000}
  let p4 = make11YearsOld p3
  ... convenient access: p4.Name, etc. ...

Here is what it would take to do this using OO today:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
type Person(name: string, age: int) =
  override it.ToString() = "Person{ name=" + name + ", age=" + age.ToString() + " }"
  member it.Name = name
  member it.Age = age
  abstract NewName: string -> Person
  default it.NewName name1 = new Person(name1, age)
  abstract NewAge: int -> Person
  default it.NewAge age1 = new Person(name, age1)

type Employee(name: string, age: int, employeeID: int) =
  inherit Person(name, age)
  member it.EmployeeID = employeeID
  override it.ToString() = "Employee{ name=" + name + ", age=" + age.ToString() + ", employeeID=" + employeeID.ToString() + " }"
  override it.NewName name1 = new Employee(name1, age, employeeID) :> Person
  override it.NewAge age1 = new Employee(name, age1, employeeID) :> Person
  member it.NewEmployeeID employeeID1 = new Employee(name, age, employeeID1)

let make11YearsOld (p:#Person) =
   p.NewAge 11

// ----- Usage -----
let p1 = new Person("Fred", 2)
let p2 = make11YearsOld p1
let p3 = new Employee(p1.Name, p1.Age, 1000)
let p4 = make11YearsOld p3

I don't see a better way to do this in F# at the moment.

~TMSteve

By on 4/17/2008 2:15 PM ()

Remark from nitpicker's corner:
> Unfortunately, records in F# are simply .NET structs
Implementation-wise, they are not. F# records are .NET classes (sealed) - that is, record type is a reference type, not a value type.

I guess that at least means that your syntax is not too difficult for code generation to cope with. However I think that it may affect typing rules in somewhat complicated way.

I do like your syntax though - seems to be rather clean and useful!

Friendly,
Dmitry

By on 4/17/2008 9:29 PM ()

Here's an alternate approach, using a DU "Person" to enumerate the kinds of Person's:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
 
#light
(* LarsJo, hubFS#5786.
  // ----- Desired Feature: Subclassing of Records ------
  type Person = { Name: string; Age: int}
  type Employee extends Person with {EmployeeID: int}
  let make11YearsOld (p:#Person) =
    {p with Age = 11}

  // ----- Usage -----
  let p1 = {Name: "Fred"; Age: 2}
  let p2 = make11YearsOld p1
  let p3 = {Name: "Sam"; Age: 21; EmployeeID: 1000}
  let p4 = make11YearsOld p3
  ... convenient access: p4.Name, etc. ...
*)  
// ----- Record + DU implementation -----

type _Person = { Name: string; Age: int }
type Person =
  | PlainPerson of _Person
  | Employee of _Person * int
  with
    member it._Person =
      match it with
      | PlainPerson p -> p
      | Employee (p,id) -> p
    member it.Name = it._Person.Name
    member it.Age = it._Person.Age
    member it.EmployeeID =
      match it with
        | Employee (p,id) -> id
        | _ -> failwith "Person.EmployeeID: Only Employees have an EmployeeID"

// This returns an entity similar to "p",
// but with "p1" as the new _person-portion.
let UpdatePerson (p:Person) (p1:_Person) =
  match p with
    | PlainPerson _ -> PlainPerson p1
    | Employee (_,id) -> Employee(p1,id)

let NewName (p:Person) name =
  UpdatePerson p {p._Person with Name = name}

let NewAge (p:Person) age =
  UpdatePerson p {p._Person with Age = age}

// Q: Is there a compile-time way to enforce that only Employee's
// can have employeeID's changed?
let NewEmployeeID (p:Person) employeeID1 =
  match p with
    | Employee (p,id) -> Employee(p,employeeID1)
    | _ -> failwith "NewEmployeeID: Only Employees have an EmployeeID"

let make11YearsOld (p:Person) =
   NewAge p 11


// ---------- tests ----------
let ps s = printfn "%s" s
let pam msg a = printfn "%s = %A" msg a

let p1 = PlainPerson({Name="Fred"; Age=2})
let p2 = make11YearsOld p1
let p3 = Employee(p1._Person, 1000)
let p4 = make11YearsOld p3
p1.Name |> pam "p1.Name"
p2 |> pam "p2"
p4 |> pam "p4"
p4.Name |> pam "p4.Name"
p4.EmployeeID |> pam "p4.EmployeeID"

printf "----- Done: Press any key. -----"
System.Console.ReadKey(false) |> ignore
By on 4/17/2008 2:37 PM ()

Learning from the previous two approaches, here is an OO approach that is somewhat simplified by using a structure.
Compared to the DU approach:
advantage: EmployeeID & NewEmployeeID usage type checked at compile time;
disadvantage: access to EmployeeID after (UpdatePerson, NewName, or NewAge) needs dynamic cast to Employee.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
 
#light
(* LarsJo, hubFS#5786.
  // ----- Desired Feature: Subclassing of Records ------
  type Person = { Name: string; Age: int}
  type Employee extends Person with {EmployeeID: int}
  let make11YearsOld (p:#Person) =
    {p with Age = 11}

  // ----- Usage -----
  let p1 = {Name: "Fred"; Age: 2}
  let p2 = make11YearsOld p1
  let p3 = {Name: "Sam"; Age: 21; EmployeeID: 1000}
  let p4 = make11YearsOld p3
  ... convenient access: p4.Name, etc. ...
*)  
// ----- Record + OO implementation -----

type _Person = { Name: string; Age: int }
// NOTE: Gives "abstract" warning.
type Person(p: _Person) =
  member it._Person = p
  member it.Name = p.Name
  member it.Age = p.Age
  abstract UpdatePerson: _Person -> Person
  member it.NewName name1 = it.UpdatePerson {p with Name=name1}
  member it.NewAge age1 = it.UpdatePerson {p with Age=age1}

type PlainPerson(p: _Person) =
  inherit Person(p)
  override it.UpdatePerson p = new PlainPerson(p) :> Person
  override it.ToString() = "PlainPerson" + any_to_string p

type Employee(p: _Person, employeeID: int) =
  inherit Person(p)
  member it.EmployeeID = employeeID
  override it.UpdatePerson p = new Employee(p,employeeID) :> Person
  member it.NewEmployeeID id1 = new Employee(p,id1)
  override it.ToString() = "Employee{ " + any_to_string p + "; EmployeeID=" + employeeID.ToString() + " }"

let make11YearsOld (p:#Person) =
   p.NewAge 11


// ---------- tests ----------
let ps s = printfn "%s" s
let pam msg a = printfn "%s = %A" msg a

let p1 = PlainPerson({Name="Fred"; Age=2})
let p2 = make11YearsOld p1
let p3 = Employee(p1._Person, 1000)
let p4 = make11YearsOld p3 :?> Employee  // dynamic cast
p1.Name |> pam "p1.Name"
p2 |> pam "p2"
p4 |> pam "p4"
p4.Name |> pam "p4.Name"
p4.EmployeeID |> pam "p4.EmployeeID"

printf "----- Done: Press any key. -----"
System.Console.ReadKey(false) |> ignore
By on 4/17/2008 3:57 PM ()

I *really* like your idealized syntax. The other approaches and threads you pointed out were really helpful too. At least, I know that other people are running into the same problem.

I wonder what the theoretical problems would be as far as adding it to the record syntax.

By on 4/17/2008 4:57 PM ()

I don't see an easy way to add it to the current implementation of a record, which is a .NET structure.

I suspect that on .NET, the way to implement it would be to turn extensible record types into classes behind the scenes. Reason: using classes would give a way to clone an object having the correct fields. This is just an implementation detail; they would look just like normal records to the programmer.

This is a general theme of mine: discriminated unions and records provide convenient concise syntax, but have limited extensibility; I would like an extensible version of these, and it looks like implementing internally as classes would be an effective technique on .NET. Just hide all that from the programmer.

Unfortunately, I don't see any way to avoid one annoyance: at the time a record is created, there would have to be some keyword you use to say that it is permitted to be extended. Reason: the current implementation is high performance and compact; don't want to burden ALL record types with the cost of being extensible.

By on 4/17/2008 8:28 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper