Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# kprintf: missing warning about about redundant arguments

Tags:

f#

Is there a way to do string formatting for Exceptions with kprintf (or similar) and still get warnings about redundant arguments? So far I have:

type MyException (s:string) = 
    inherit System.Exception(s)
    static member Raise msg =   
        Printf.kprintf (fun s -> raise (MyException(s))) msg
        
do
    MyException.Raise "boom %d" 9 1 // Gives NO warning about redundant arguments
    failwithf         "boom %d" 9 1 // Gives warning about redundant arguments

I know I could use the new $ formatting but would like to stay compatible with older versions of F#.

like image 686
Goswin Rothenthal Avatar asked Jan 18 '26 18:01

Goswin Rothenthal


2 Answers

The problem does not come from kprintf, but from raise, which allows the return type of the function to be any type, including a curried function. The function failwithf has this warning as a special case.

If you return, let’s say, a string or a unit, you would actually get an error. The downside is that now you cannot raise in every position anymore, as then the return argument is not generic anymore.

You could fix that by forcing a type argument, but that would make it less than ideal for the caller.

For instance, this works, but if you change the return type of f to be a string, it fails:

open System
type MyException (s:string) = 
    inherit System.Exception(s)
    static member Raise msg =   
        Printf.kprintf (fun s -> raise (MyException s) |> ignore) msg
        
module Test =
    let f() : unit = // string won’t work
         MyException.Raise "boom %d" 9 10 // error, as expected

Edit: workaround

Here's some kind of a workaround. You can use generic type constraints to limit the type, and use the fact that an F# function type is not comparable or equatable, does not have a proper value null.

Most types you'll use are equatable, because of F#'s powerful structural equality support. Other options for a type constraint are struct (only structs), null (this excludes record and DU types) or a certain inheritance constraint if that serves your use case.

Example:

type MyException (s:string) = 
    inherit System.Exception(s)

    static member Raise<'T when 'T : equality> msg =   
        let raiser s =
            raise (MyException s)
            Unchecked.defaultof<'T> // fool the compiler

        Printf.kprintf raiser msg
        
module Test =
    let f() = MyException.Raise "boom %d" 9
    let g() = MyException.Raise "boom %d" 9 10  // error

Downside, apart from the type restriction, is that the error you get will be a type error, not a warning as you requested.

like image 62
Abel Avatar answered Jan 21 '26 07:01

Abel


The check for this warning in the compiler specifically looks for a number of known F# library functions. You can see the checks in the compiler source code and here (the functions are raise, failwith, failwithf, nullArg, invalidOp and invalidArg).

The reason for this is that, there is, in principle, nothing wrong with a generic function that returns 'T and is used so that 'T is inferred to be a function. For example, it is perfectly fine to do:

let id f = f
id sin 3.14

Here, we define a function id with one argument, but if I give it a function as an argument, it also becomes valid to call it with an extra argument as id sin 3.14.

The special functions are special in that they look like they return a value of a generic type, but they never actually return, because they raise an execption. The compiler cannot, in general, know whether that is the case for any custom function or method you yourself write - and so it only checks this for known special functions. (It would have to do more sophisticated analysis, possibly marking the return type of these functions as the bottom type, but that is beyond what F# does...).

like image 40
Tomas Petricek Avatar answered Jan 21 '26 09:01

Tomas Petricek



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!