Type Elmish.WPF main() function reports binding () -> #Window as an error

99 Views Asked by At

Why does my version of the Elmish.WPF Sample NewWindow (XAML code and F# Core) emit the error ...

  • The type 'unit -> 'a' is not compatible with the type 'Window'.

The same line emits the warning...

  • This construct causes code to be less generic than indicated by its type annotations. The type variable implied by the use of a '#', '_' or other type annotation at or near <line reference to createWindow_Window2 in the let bindings = statement">

Why am I getting this error and warning?

What I am doing is merging the Elmish.WPF Samples SingleCounter (XAML code and F# Core) and NewWindow (XAML code and F# Core) to have the Model, bindings(), and Msg parts in Program.fs instead of App.fs (as it was in the NewWindow sample).

My goal is to make a SimpleCounter able to open a NewWindow.

The XAML code passes Func<Window2> into the F# code here...

let main mainWindow (createWindow2: Func<#Window>)

...I define bindings as...

let bindings = Platform.bindings createWindow_Window2

I am down to the one compiler error I mentioned above (and that also appears in line beginning "let bindings = ..." below)...

let main mainWindow (createWindow_Window2: Func<#Window>) =

  let logger =
      LoggerConfiguration()
            .MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose)
            .MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose)
            .MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose)
            .WriteTo.Console()
            .CreateLogger()
  let createWindow_Window2 = 
      let window = createWindow_Window2.Invoke()
      window.Owner <- mainWindow
      window

  let bindings = Platform.bindings createWindow_Window2
  WpfProgram.mkProgramWithCmdMsg (fun _ -> m_init, []) update bindings toCmd
    |> WpfProgram.withLogger (new SerilogLoggerFactory(logger))
    |> WpfProgram.startElmishLoop mainWindow

The top of Program.bindings is defined as ...

 let bindings (createWindow_Window2: unit -> #Window) () : Binding<Model, Msg> list = [
        "Window_Window2_Show|> Binding.cmd Window_AboutProduct_Show
        "Window_Window2" |> Binding.subModelWin(
                                                Window_Window2.get >> WindowState.ofOption,
                                                snd,
                                                Window_Window2ct.mapInOutMsg,
                                                Window_Window2_Module.Window_Window2.bindings,
                                                createWindow_Window2,
                                                isModal = true)
...bindings continue but are not relevant to this question...

... and reports no errors!

The compiler seems to think Platform.bindings is expecting unit -> `a when createWindow_Window2 is correctly typed as unit -> #Window.

You can see the call into Program.fs:main is correctly called from App.xaml.cs with a lambda function returning a Window2 as follows...

        private void StartElmish(object sender, EventArgs e)
        {
            this.Activated -= StartElmish;
            Program.main(MainWindow, () => new Window2());
        }

So my question is why am I getting this mismatch type error when it seems I am passing the correct types?

Thank you!

2

There are 2 best solutions below

0
rfreytag On BEST ANSWER

A big thank you to: Brian Berns, Bent Tranberg, and Tomas Petricek for helping me!

The answer to my problem was astonishingly simple to an expert but to a relative novice only yielded after meticulous comparison with the working Elmish.WPF code for NewWindow.

For easy comparison I have pushed up to rfreytag/Elmish.WPF a version of the NewWindow (XAML code and F# Core) sample that compiles and runs (REMEMBER to build the NewWindow sample).

This version of NewWindow (XAML code and F# Core) when compiled shows the error and warning I reported above …

The type 'unit -> 'a' is not compatible with the type 'Window'. 
See also C:\Workspace\Elmish.WPF\src\Samples\AnotherNewWindow.Core\Program.fs(78,72)-(78,79).

...and the warning...

This construct causes code to be less generic than indicated by its 
type annotations. The type variable implied by the use of a '#', '_' 
or other type annotation at or near 
'C:\Workspace\Elmish.WPF\src\Samples\AnotherNewWindow.Core\Program.fs(78,72)-(78,79)' 
has been constrained to be type 'unit -> 'a'.

To see the precise fix you can compare the working and breaking branches on my copy of Elmish.WPF. Which is that I had forgotten the () following the definition of let createWindow_Window2 = ...

let createWindow_Window2 = 
      let window = createWindow_Window2.Invoke()
      window.Owner <- mainWindow
      window

... returns the function () -> Window while the correct ...

let createWindow_Window2 () = 
      let window = createWindow_Window2.Invoke()
      window.Owner <- mainWindow
      window

... returns the needed Window.

A newbie looks at unit → ‘a and doesn’t immediately recognize it as a function. And of course, functions are not C# Window objects.

Did not help me that the error message doesn’t follow the F# form of...

expecting a <type sought by context> 
but was given a <passed in type>

...which has become familiar from working with the F# compiler.

Not sure why this message diverged from that pattern. Maybe someone can explain that?

The warning is alerting to the less-specific #Window (see # ‘flexible type’ definition) possibly clashing at run-time with the unit → ‘a function.

As usual, learning a new framework means learning the compiler and linker messages.

1
Tomas Petricek On

I think one issue in your code is that the name createWindow_Window2 is used both as the name of an argument of main, but then later also as a local variable in the function of differnt type. Another issue is that in one place, you try to use a delegate type Func<#Window> but in another place, you have an ordinary F# function Window -> unit.

My advice would be to use Window -> unit everywhere and avoid variable shadowing:

let main mainWindow (createWindow_Window2:Window -> unit) = (* Changed type here *)
  let logger = (* omitted *)
  let createWindowWithOwner () =  
        (* Renamed and added '()' so that it is a function *)
      let window = createWindow_Window2 () (* Just function call *)
      window.Owner <- mainWindow
      window

  let bindings = 
    Platform.bindings createWindowWithOwner (* Pass the right function here *)
  (* omitted *)

Related Questions in F#