objects |> functions

Tres - Step 1

Here, we're making the leap to F#. Once we ensure that our project file is named Tres.fsproj, the contents of the file should be the same as they were for Dos, just with different namespaces. We'll also need to specify a dependency on F#'s core library, so we'll add the following to paket.dependencies:

nuget FSharp.Core

...and copy paket.references over from Dos. Once it's in our project, add an FSharp.Core line to it.

This is a good time to talke about F# project structure. F# projects are historically not split into directories, as compilation order is significant, and having them in the same directory allows the tooling to ensure that the compilation order is preserved. With the structure of the .fsproj file, this is not necessarily a limitation (as you can edit the .fsproj file directly to specify subdirectory compilation order), but we'll follow it for our purposes here.

The module is created as HomeModule.fs in the project root:

namespace Tres

open Nancy

type HomeModule () as this =
  inherit NancyModule ()

    this.Get("/", fun _ -> "Hello World from Nancy F#")

If you look at Dos, you can see how the translation occurred:

  • "using" becomes "open", and moves inside the namespace delaration
  • F# does not express constructors in the way C# folks are used to seeing them. Parameters to the class are specified in the type declaration (or a new function, which we don't need for our purposes), and then are visible throughout the class.
  • Since we don't have an explicit constructor where we can wire up the Get() method call, we accomplish it using a do binding; this is code that will be run every time the class is instantiated. The as this at the end of type HomeModule() allows us to use this to refer to the current instance; otherwise, do cannot see it.
  • This also illustrates the syntax differences in defining lambdas between C# and F#. F# uses the fun keyword to indicate an anonymous function. The _ is used to indicate that we do not care what the parameter is; since this request doesn't require anything from the DynamicDictionary Nancy provides, we don't.

We rename Program.fs to App.fs, and in this file, we provide the contents from both Startup.cs and App.cs.

namespace Tres

open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Nancy.Owin

type Startup() =
  member __.Configure (app : IApplicationBuilder) =
    app.UseOwin (fun x -> x.UseNancy (fun x -> ()) |> ignore) |> ignore

module App = 
  let main argv = 
    use host = (new WebHostBuilder()).UseKestrel().UseStartup<Startup>().Build ()
    host.Run ()

The Startup class is exactly the same as the C# version, though it appears much differently. The UseNancy() method returns quite a complex result, but the parameter to the UseOwin() method expects an Action<>; by definition, this returns void*. In F#, there is no implicit throwaway of results**; you must explicitly mark results that should be ignored. UseNancy also expects an Action<>, so we end up with an extra lambda and two ignores to accomplish the same thing.

The App module is also new. F# modules can be thought of as static classes (if you use one from C#, that's what they look like). An F# source file must start with either a namespace or module declaration; also, any code (let, do, member, etc.) cannot be simply in a namespace. We start with the Tres namespace so that our Startup class's full name will be Tres.Startup, so we have to define a module for our let binding / entry point.

At this point, dotnet build will fail. I mentioned compilation order earlier; we've added one file and renamed the other, but we have yet to tell the compiler about them, or how they should be ordered. Back in Tres.fsproj, modify the ItemGroup that has a <Compile Include="Program.fs" />, to look like the following:

  <Compile Include="HomeModule.fs" />
  <Compile Include="App.fs" />

(In the future, we'll add updating this list to our discipline of creating a new file.)

Now, we can execute dotnet run, watch it start, visit localhost:5000, and see our F# message.

Back to Step 1

* The unit type in F# is the parallel to this, but there's more to it than just "something else to call void."

** For example, StringBuilder.Append() returns the builder so you can chain calls, but it also mutates the builder, and you don't have to provide a variable assignment for every call. In F#, you would either need to provide that, or pipe the output (|>) to ignore.

Multiple items
namespace FSharp

namespace Microsoft.FSharp
namespace Microsoft.FSharp.Core
namespace Tres
namespace Nancy
Multiple items
type HomeModule =
  inherit NancyModule
  new : unit -> HomeModule

new : unit -> HomeModule
val this : HomeModule
Multiple items
type NancyModule =
  member After : AfterPipeline with get, set
  member Before : BeforePipeline with get, set
  member Context : NancyContext with get, set
  member Delete : path:string * action:Func<obj, obj> * ?condition:Func<NancyContext, bool> * ?name:string -> unit + 5 overloads
  member Get : path:string * action:Func<obj, obj> * ?condition:Func<NancyContext, bool> * ?name:string -> unit + 5 overloads
  member Head : path:string * action:Func<obj, obj> * ?condition:Func<NancyContext, bool> * ?name:string -> unit + 5 overloads
  member ModelBinderLocator : IModelBinderLocator with get, set
  member ModelValidationResult : ModelValidationResult with get, set
  member ModulePath : string with get, set
  member Negotiate : Negotiator

NancyModule() : NancyModule
NancyModule(modulePath: string) : NancyModule
NancyModule.Get<'T>(path: string, action: System.Func<obj,System.Threading.CancellationToken,System.Threading.Tasks.Task<'T>>,?condition: System.Func<NancyContext,bool>,?name: string) : unit
NancyModule.Get(path: string, action: System.Func<obj,System.Threading.CancellationToken,System.Threading.Tasks.Task<obj>>,?condition: System.Func<NancyContext,bool>,?name: string) : unit
NancyModule.Get<'T>(path: string, action: System.Func<obj,System.Threading.Tasks.Task<'T>>,?condition: System.Func<NancyContext,bool>,?name: string) : unit
NancyModule.Get(path: string, action: System.Func<obj,System.Threading.Tasks.Task<obj>>,?condition: System.Func<NancyContext,bool>,?name: string) : unit
NancyModule.Get<'T>(path: string, action: System.Func<obj,'T>,?condition: System.Func<NancyContext,bool>,?name: string) : unit
NancyModule.Get(path: string, action: System.Func<obj,obj>,?condition: System.Func<NancyContext,bool>,?name: string) : unit
namespace Microsoft
namespace Microsoft.AspNetCore
namespace Microsoft.AspNetCore.Builder
namespace Microsoft.AspNetCore.Hosting
namespace Nancy.Owin
Multiple items
type Startup =
  new : unit -> Startup
  member Configure : app:IApplicationBuilder -> unit

new : unit -> Startup
val app : IApplicationBuilder
type IApplicationBuilder =
  member ApplicationServices : IServiceProvider with get, set
  member Build : unit -> RequestDelegate
  member New : unit -> IApplicationBuilder
  member Properties : IDictionary<string, obj>
  member ServerFeatures : IFeatureCollection
  member Use : middleware:Func<RequestDelegate, RequestDelegate> -> IApplicationBuilder
(extension) IApplicationBuilder.UseOwin() : System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>,System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>>>
(extension) IApplicationBuilder.UseOwin(pipeline: System.Action<System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>,System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>>>>) : IApplicationBuilder
val x : System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>,System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>>>
(extension) System.Action.UseNancy(action: System.Action<NancyOptions>) : System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>,System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>>>
(extension) System.Action.UseNancy(?options: NancyOptions) : System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>,System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>>>
val x : NancyOptions
val ignore : value:'T -> unit
module App

from Tres
Multiple items
type EntryPointAttribute =
  inherit Attribute
  new : unit -> EntryPointAttribute

new : unit -> EntryPointAttribute
val main : argv:string [] -> int
val argv : string []
val host : IWebHost
Multiple items
type WebHostBuilder =
  new : unit -> WebHostBuilder
  member Build : unit -> IWebHost
  member ConfigureAppConfiguration : configureDelegate:Action<WebHostBuilderContext, IConfigurationBuilder> -> IWebHostBuilder
  member ConfigureServices : configureServices:Action<IServiceCollection> -> IWebHostBuilder + 1 overload
  member GetSetting : key:string -> string
  member UseSetting : key:string * value:string -> IWebHostBuilder

WebHostBuilder() : WebHostBuilder
(extension) IWebHost.Run() : unit
Fork me on GitHub