1
votes

I want to define the following record type:

type LogFuncs = {
    LogWithLabel : string -> 'a -> unit
    LogWithLabelAndReturn : string -> 'a -> 'a }

The intention is that I can define one function of string -> unit and then use that to derive several convenience functions, like so:

let makeLogFuncs outputFunc =
    let logWithLabelFunc label value = sprintf "%s: %A" label value |> outputFunc
    let logWithLabelAndReturnFunc label value = logWithLabelFunc label value; value
    {   LogWithLabel = logWithLabelFunc
        LogWithLabelAndReturn = logWithLabelAndReturnFunc  }

But, the compiler won't let me do this without specifying an <'a> when making an instance of type LogFuncs, and that's not what I want to do -- I want to be able to call this function on any 'a. (I will also want to provide related functions, hence the use of the record type.)

Is it possible to define a record type with a field containing type parameter that is not also a type parameter of the record type itself?

2
Where would you store the information on what 'a acdtually is and how would you use it in a type safe way. If you don't care about type safety just use object - John Palmer
I don't necessarily care about the type of 'a when I'm logging it, but when I'm logging it and then returning it, I want the return type to be exactly the same as the input type, not obj. - Overlord Zurg
IMO interfaces (where you can do it) and object-expressions (to create such an interface) are a viable alternative to records here - some time ago I blogged about something very similar - maybe you find it interesting - Random Dev

2 Answers

3
votes

I don't believe it is possible to do with record types. But I can define a class which provides the mechanism I wanted:

type Logger (outputFunc: string->unit) =
    member __.LogWithLabel label value = sprintf "%s: %A" label value |> outputFunc
    member x.LogWithLabelAndReturn label value = x.LogWithLabel label value; value

Then I can do:

let log = new Loggery (printfn "%s")

let ``result should equal 5`` = 5 |> log.LogWithLabelAndReturn "Beans"

...and it correctly prints "Beans: 5" and returns 5.

0
votes

You can make the record itself generic:

type LogFuncs<'a> = {
    LogWithLabel : string -> 'a -> unit
    LogWithLabelAndReturn : string -> 'a -> 'a }

That also makes the makeLogFuncs generic. It's still usable, but probably not in the way you want:

(makeLogFuncs (printfn "%s")).LogWithLabel "Number" 42
(makeLogFuncs (printfn "%s")).LogWithLabelAndReturn "Number" 42
(makeLogFuncs (printfn "%s")).LogWithLabel "Text" "Foo"
(makeLogFuncs (printfn "%s")).LogWithLabelAndReturn "Text" "Foo"

As the answer provided by Overlord Zurg implies, the OP approach is quite object-oriented, so use objects if you want to design the system in that way.