hubFS: THE place for F#

. . . are you on The Hub?
Welcome to hubFS: THE place for F# Sign in | Join | Help
in Search

How does Printf typing work?

Last post 08-05-2008, 10:16 by dsyme. 3 replies.
Sort Posts: Previous Next
  •  06-24-2008, 19:04 6194

    How does Printf typing work?

    Hello, I've been loving F# so far, but I've run into something I'm quite puzzled over. My goal is to bind a function to write to a specific text writer. In the following code, test1 compiles, but both test2 and 3 fail with the same error (on pn "test"): "This expression has type  unit but is here used with type  string -> unit".

    The error changes depending on the first use of pn, i.e., if I do pn with a simple string and no formatters, then it changes and tell me I'm missing an argument. So, somehow the first usage of the pn function is giving it its type, and I can't figure out why.

    Would someone please enlighten me as to what's going on here?

    #light
    open System
       
    let test1 () =
        let tw = new IO.StringWriter()
        let pn fmt = Printf.twprintfn tw fmt
        pn "test %s" "test"
        pn "test"
        tw.ToString()
       
    let getprint () =
        let tw = new IO.StringWriter()
        let pn fmt = Printf.twprintfn tw fmt
        (tw,pn)

    let test2 () =
        let tw,pn = getprint ()
        pn "test %s" "test"
        pn "test"
        tw.ToString()

    let test3 () =
        let tw = new IO.StringWriter()
        let pn = Printf.twprintfn tw
        pn "test %s" "test"
        pn "test"
        tw.ToString()   

  •  06-26-2008, 11:08 6208 in reply to 6194

    Re: How does Printf typing work?

    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:

    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:

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

    whereas if you comment the first pn statement it becomes:

    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

  •  06-27-2008, 17:01 6229 in reply to 6208

    Re: How does Printf typing work?

    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:



    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

     

     

  •  08-05-2008, 10:16 6517 in reply to 6229

    Re: How does Printf typing work?

    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.

     

    #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.

     

     

    #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

     

     

View as RSS news feed in XML
Powered by Community Server, by Telligent Systems