I suppose it would help to search. I found this thread from 2006 which seems to answer my question (with an answer I'm not fond of [;)]):

[link:cs.hubfs.net]

By on 4/24/2008 6:36 PM ()

If you really need it, you can use this inline syntax (it is used in standard library for operators):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> type foo() =
  member f.id (a: int) = a + 1
let f = foo();;

type foo = class
           end
           with
             new : unit -> foo
             member id : a:int -> int
           end
val f : foo

> let inline id_of (s: ^src) (x: 'a) : 'b =
     (^src : (member id : 'a -> 'b)(s, x));;

val inline id_of :  ^src -> 'a -> 'b when  ^src : (member id :  ^src * 'a -> 'b)

> id_of f 4;;
val it : int = 5
By on 4/25/2008 7:26 AM ()

That's kinda neat, and appears to work, but I don't really understand what it's doing. Is there a description of that technique somewhere?

By on 4/25/2008 10:10 AM ()

The inline keyword is probably covered in Expert F#. However, it is intended for individual non-recursive functions and will not scale to anything more significant. So it is not intended to be a replacement for a structurally-typed object system!
You may also try using reflection but that will not provide the static typing that OCaml does.

By on 4/25/2008 10:56 AM ()

Oh, found it in my PDF copy of the book. "inline" isn't in the index of my hard copy, so I hadn't seen it before.

It even explains the

1
(^src: (member...))

thing which previously looked like gibberish to me.

Thanks.

By on 4/25/2008 11:00 AM ()

MrKurt, using "inline / member" will only be helpful in limited circumstances (IMHO).

Besides the option of using .NET reflection, there are two general mechanisms in F# for working across several types:

1. Define an interface, and implement it on each type; OR

2. Wrap the types into a DU, and define a function that uses match to handle each of the types.

Here is (1) an "interface" solution:

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
 
#light
type has_foobar =
  abstract foo: unit -> string
  abstract bar: int -> int

type ta() = 
  member x.foo() = "my name is a"
  member x.bar (n:int) = n + n
  with interface has_foobar with
    member x.foo() = x.foo()  // Invoke the member above.
    member x.bar n = x.bar n  // Invoke the member above.

type tb() = 
  member x.foo() = "my name is b"
  member x.bar (n:int) = n * n
  with interface has_foobar with
    member x.foo() = x.foo()  // Invoke the member above.
    member x.bar n = x.bar n  // Invoke the member above.

// These functions accept either ta or tb.
let foo fb = (fb :> has_foobar).foo()
let bar fb (n:int) = (fb :> has_foobar).bar n

// --- Define test data ---
let a = new ta()
let b = new tb()

And here is (2) a "DU wrapper" solution:

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
 
#light
type ta() = 
  member x.foo() = "my name is a"
  member x.bar (n:int) = n + n

type tb() = 
  member x.foo() = "my name is b"
  member x.bar (n:int) = n * n

type wrap =
  | Ta of ta
  | Tb of tb

// These functions accept a wrapped ta or tb.
let foo (w:wrap) =
  match w with
    | Ta a -> a.foo()
    | Tb b -> b.foo()

let bar (w:wrap) (n:int) =
  match w with
    | Ta a -> a.bar(n)
    | Tb b -> b.bar(n)

// --- Define test data ---
let a = Ta (new ta())
let b = Tb (new tb())

Comparing these two approaches, (1)'s advantage is that defining data is simple: "new ta()".

(2)'s advantage is that it can be added independently of the type definitions; however usage requires wrapping the objects, so definition becomes: "Ta (new ta())"

Either way, once the basic functions "foo x" and "bar x n" are defined, the two approaches can be used identically:

1
2
3
4
5
6
7
8
9
10
11
12
13
// A function that invokes both foo and bar.
let foobar x (n:int) = (foo x, bar x n)
  
let pam msg a = printfn "%s = %A" msg a
foo a |> pam "foo a"
foo b |> pam "foo b"
bar a 5 |> pam "bar a 5"
bar b 5 |> pam "bar b 5"
foobar a 5 |> pam "foobar a 5"
foobar b 5 |> pam "foobar b 5"

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

Wouldn't you prefer something similar to the OCaml syntax, though? I sure would.

Having to keep interfaces in sync with the actual classes I'm writing annoys me. A more implicit mechanism would go a long way to keeping my code simple and expressive, I think.

By on 5/1/2008 7:54 AM ()

I think the problem is that .Net is based on nominative subtyping, not structural subtyping.

Of course it's still possible. A structural type could be implemented e.g. as a record of functions under the cover, but haven't heard that something like this is planned.

By on 5/5/2008 4:43 AM ()

Oh, and if I understand what I'm reading correctly, something like this works in OCaml:

1
let id_of v = v#id

That's what I want. [:)]

By on 4/24/2008 6:31 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