The problem here is about type generalization. In test1 the compiler knows the original definition of pn and if you use F# and VS you'll get a tooltip that the pn has type (#Printf.TextWriterFormat<'b> -> b'). When you apply pn in the two statements the generic function gets specialized according to input arguments.

The signature of getprint() is:

1
val getprint : unit -> IO.StringWriter * (#Printf.TextWriterFormat<'b> -> 'b)

in test2 and test3 the function gets specialized using the first application and the type inferred in test3 is:

1
Format<(string, unit), IO.TextWriter, unit, unit, string> -> string -> unit

whereas if you comment the first pn statement it becomes:

1
Format<(string, unit), IO.TextWriter, unit, unit, string>  -> unit

The overall point is that part of the type of pn is inferred by the first use.

Antonio

By on 6/26/2008 11:08 AM ()

RIght. A good rule of thumb in F# is that "only things declared as functions, types or members are truly generic". Here "pn" is not truly generic - it is a single function object that can only be used at one particualr type.

The particularly interesting thing about your example is your are trying to return two things

(1) a text writer

(2) a generic function associated with that text writer

You're trying to return these two things as a tuple, which doesn't work, because tuples can't contain things that are truly generic in their own right.

So, one way around this is to create an object that combines these two things and offers a generic method. Here's how:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 

type Printer() =
    let tw = new IO.StringWriter()
    member x.TextWriter = tw
    member x.Print fmt = Printf.twprintfn tw fmt
 
let test2 () =
    let pr = Printer()
    pr.Print "test %s" "test"
    pr.Print  "test"
    pr.TextWriter.ToString()

Here Print is a generic method (i.e. is generic in its own right). A very neat example of a good use of generic methods.

Kind regards

don

By on 6/27/2008 5:01 PM ()

A follow up question I got in email was:

I cant find any references as to how to do the following:
Pass a function that takes the printf arguments – so I can change where things are printed to.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#light
 
// This works and creates two instances of the function
printfn "%d %d" 1 2
printfn "%d %f" 3 4.0
 
let me (p: #Printf.TextWriterFormat<'a> -> 'a) =
    p "%d %d" 1 2
   // p "%d" 3          ß uncommenting gives a type error as the previous line has fixed ‘a
   
me printfn
// I’d like to do
// me eprintfn
// and
// me (fprintfn stream)


Is this possible ?

Here's the answer:

You pass an object implementing an interface with a generic method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#light
 
type Printer =
    abstract Print : #Printf.TextWriterFormat<'a> -> 'a
 
// This works and creates two instances of the function
printfn "%d %d" 1 2
printfn "%d %f" 3 4.0
 
let me (p: Printer) =
    p.Print "%d %d" 1 2
    p.Print "%d %f" 2 4.0
 
let printer1 = { new Printer with member x.Print fmt = printf fmt }
let printer2 = { new Printer with member x.Print fmt = eprintf fmt }

me printer1
me printer2


don

By on 8/5/2008 10:16 AM ()
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