This is value restriction.
When you declare warn as a plain function, it's just a generic function, no fuss. But when you declare it without parameters, it becomes a value (it's a value of function type, but still a value; there is a subtle difference), and so is subject to value restriction, which in a nutshell says that values cannot be generic.
Try removing all usages of warn (not just the first one). You'll get the compiler complain about this: Value restriction. The value 'warn' has been inferred to have generic type. Follow the link at the top for more discussion of this.
Of course this is a bit too strong, so F# has a little relaxation of this rule: if the value is used in the vicinity of its declaration, the compiler will fix generic arguments based on the usage. This is why it works with the first usage, but not with the second. You understood that part correctly.
One workaround would be to add explicit arguments, thus making warn a "declared function", not "value of function type". Try this:
let warn() = getWarn log4net.LogManager.GetLogger("bar")
Another workaround (which is really the same thing - see below) is to declare the generic argument explicitly, and also add a type annotation:
let warn<'a> : StringFormat<'a, unit> -> 'a = log4net.LogManager.GetLogger("bar")
The type annotation is necessary in this case, because otherwise the compiler doesn't know how the generic argument relates to the value type.
This way, you can use it just as if it was a true generic value:
warn "This is a warning"
warn "The value of my string is %s" someString
But there is a catch: this trick is really the same as adding a unit parameter (the previous workaround, above). Behind the scenes, the compiler will emit this definition as a true declared generic function and give it a single unit argument. The implication of this (as well as the previous workaround) is that every time you use warn, it gets invoked - i.e. every time you call warn, you call getWarn and GetLogger, too. In this particular example, this is probably ok, but if you're doing some significant work before producing the return function, that work will get re-done on every call.
And this points us to a deeper problem with your approach: you're trying to pass around functions without losing their genericity. Can't do that. Can't have a function value, return it from a function or pass it to a function, and still have it remain generic. Try this:
let mapTuple f (a,b) = (f a, f b)
let makeList x = [x]
mapTuple makeList (1, "abc")
Instinctively one would expect this to work and produce a tuple of lists - ( [1], ["abc"] ), right? Well, it doesn't work like this. When you declare mapTuple f (a,b), that f parameter has to be of some particular type. It can't be of an open generic type, so that you could apply it to a and b even if they're of different types. Instead, the inference works the other way: since f is of one type, thinks the compiler, and it gets applied to both a and b, then a and b must be of the same type. And so the signature gets inferred to mapTuple: ('a -> 'b) -> 'a*'a -> 'b*'b.
So the bottom line is, if you just want to produce a warn function, just give it a parameter and you'll be fine. However, if you want to produce two at once (as you mentioned in the comments), you're out of luck: they would have to end up being of the same type and you can't call them as generic functions after they're produced.
If you want to have a function that returns a function (or several) without losing its genericity, you'll have to return an interface. Member functions on interfaces can be generic independently of the genericity of the interface itself:
type loggers =
abstract member warn: Printf.StringFormat<'a, unit> -> 'a
abstract member err: Printf.StringFormat<'a, unit> -> 'a
let getLoggers (log: log4net.ILog) =
{ new loggers with
member this.warn format = Printf.ksprintf log.Warn format
member this.err format = Printf.ksprintf log.Error format }
let logs = getLoggers (log4net.LogManager.GetLogger("Log"))
logs.warn "This is a warning"
logs.warn "Something is wrong: %s" someString
getWarnfunction like this:let getWarn (logger: log4net.ILog) format = Printf.ksprintf logger.Warn format. Does that let the compiler's type inference figure out what you're going for? If so, then you're hitting some kind of issue where it can figure out an argument's type in a classic function declaration, but not when that same argument is in a lambda expression. - rmunnformatparameter of yourgetWarnfunction? Is it aStringFormat<'T,'Result>? And what are the types you're seeing for thewarnandgetWarnversions of your function? - rmunnwarn: StringFormat<'a,unit> -> 'a,getWarn: ILog -> StringFormat<'a,unit> -> 'aand if I supplyformatto the getWarn function, it gets the type of'a- Nils Magne Lunde