objects () |> functions


Tres - Step 2

As we make the leap to F#, we're changing things around significantly. Remember our discussion about the flat structure of an F# project? Instead of an Entities directory with a lot of little files, we'll define a single Entities.fs file in the root of the project. Don't forget to add it to the list of compiled files in project.json; it should go above HomeModule.fs.

Next up, we will change the static classes that we created to eliminate magic strings into modules. The AuthorizationLevel type in C# looked like:

1: 
2: 
3: 
4: 
5: 
6: 
public static class AuthorizationLevel
{
    const string Administrator = "Administrator";

    const string User = "User";
}

The F# version (within the namespace Tres.Entities):

1: 
2: 
3: 
4: 
5: 
6: 
[<RequireQualifiedAccess>]
module AuthorizationLevel =
  [<Literal>]
  let Administrator = "Administrator"
  [<Literal>]
  let User = "User"

The RequireQualifiedAccess attribute means that this module cannot be opened, which means that Administrator cannot ever be construed to be that value; it must be referenced as AuthorizationLevel.Administrator. The Literal attribute means that these values can be used in places where a literal string is required. (There is a specific place this will help us when we start writing code around these types.) Also of note here is the different way F# defines attributes from the way C# does; instead of [ ] pairs, we use [< >] pairs.

We are also going to change from class types to record types. Record types can be thought of as structs, though the comparison is not exact; record types are reference types, not value types, but they cannot be set to null in code (huge caveat which we'll see in the next step) unless explicitly identified. We're also going to embrace F#'s immutability-by-default qualities that will save us a heap of null checks (as well as those pesky situations where we forget to implement them).

As a representative example, consider the Page type. In C#, it looks like this:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
namespace Uno.Entities
{
    using Newtonsoft.Json;
    using System.Collections.Generic;
    
    public class Page
    {
        [JsonProperty("id")]
        public string Id { get; set; }
        
        public string WebLogId { get; set; }
        
        public string AuthorId { get; set; }
        
        public string Title { get; set; }
        
        public string Permalink { get; set; }
        
        public long PublishedOn { get; set; }
        
        public long UpdatedOn { get; set; }
        
        public bool ShowInPageList { get; set; }
        
        public string Text { get; set; }
        
        public ICollection<Revision> Revisions { get; set; } = new List<Revision>(); 
    }
}

It contains strings, for the most part, and a Revisions collection. Now, here's how we'll implement this same thing in F#:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
namespace Tres.Entities

open Newtonsoft.Json
//...
type Page = {
  [<JsonProperty("id")>]
  Id : string
  WebLogId : string
  AuthorId : string
  Title : string
  Permalink : string
  PublishedOn : int64
  UpdatedOn : int64
  ShowInPageList : bool
  Text : string
  Revisions : Revision list
  }
with
  static member Empty = 
    { Id             = ""
      WebLogId       = ""
      AuthorId       = ""
      Title          = ""
      Permalink      = ""
      PublishedOn    = 0L
      UpdatedOn      = 0L
      ShowInPageList = false
      Text           = ""
      Revisions      = []
      }

The field declarations immediately under the type declaration mirror those in our C# version; since they are fields, though, we don't have to define getters and setters.

F# requires record types to always have all fields defined. F# also provides a with statement (separate from the one in the code above) that allows us to create a new instance of a record type that has all the fields of our original ones, only replacing the ones we specify. So, in C#, while we can do something like

1: 
var pg = new Page { Title = "Untitled" };

, leaving all the other fields in their otherwise-initialized state, F# will not allow us to do that. This is where the Empty static property comes in; we can use this to create new pages, while ensuring that we have sensible defaults for all the other fields. The equivalent to the above C# statement in F# would be

1: 
let pg = { Page.Empty with Title = "Untitled" }

. Note the default values for Permalink: in C#, it's null, but in F#, it's an empty string. Now, certainly, you can use String.IsNullOrEmpty() to check for both of those, but we'll see some advantages to this lack of nullability as we continue to develop this project.

A few syntax notes: - [] represents an empty list in F#. An F# list (as distinguished from System.Collections.List or System.Collections.Generic.List<T>) is also an immutable data structure; it consists of a head element, and a tail list. It can be constructed by creating a new list with an element as its head and the existing list as its tail, and deconstructed by processing the head, then processing the head of the tail, etc. (There are operators and functions to support that; we'll definitely use those as we go along.) Items in a list are separated by semicolons; [ "one"; "two"; "three" ] represents a string list with three items. It supports most all the collection operations you would expect, but there are some differences. - While not demonstrated here, arrays are defined between [| |] pairs, also with elements separated by semicolons.

Before continuing on to Quatro, you should familiarize yourself with the types in this step.

Back to Step 2

namespace Tres
namespace Tres.Entities
Revision.AsOf: int64
Multiple items
val int64 : value:'T -> int64 (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int64

--------------------
type int64 = System.Int64

Full name: Microsoft.FSharp.Core.int64

--------------------
type int64<'Measure> = int64

Full name: Microsoft.FSharp.Core.int64<_>
Revision.Text: string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
static member Revision.Empty : Revision

Full name: Tres.Entities.Revision.Empty
Multiple items
type RequireQualifiedAccessAttribute =
  inherit Attribute
  new : unit -> RequireQualifiedAccessAttribute

Full name: Microsoft.FSharp.Core.RequireQualifiedAccessAttribute

--------------------
new : unit -> RequireQualifiedAccessAttribute
module AuthorizationLevel

from Tres.Entities
Multiple items
type LiteralAttribute =
  inherit Attribute
  new : unit -> LiteralAttribute

Full name: Microsoft.FSharp.Core.LiteralAttribute

--------------------
new : unit -> LiteralAttribute
val Administrator : string

Full name: Tres.Entities.AuthorizationLevel.Administrator
val User : string

Full name: Tres.Entities.AuthorizationLevel.User
namespace Newtonsoft
namespace Newtonsoft.Json
type Page =
  {Id: string;
   WebLogId: string;
   AuthorId: string;
   Title: string;
   Permalink: string;
   PublishedOn: int64;
   UpdatedOn: int64;
   ShowInPageList: bool;
   Text: string;
   Revisions: Revision list;}
  static member Empty : Page

Full name: Tres.Entities.Page
Multiple items
type JsonPropertyAttribute =
  inherit Attribute
  new : unit -> JsonPropertyAttribute + 1 overload
  member DefaultValueHandling : DefaultValueHandling with get, set
  member IsReference : bool with get, set
  member ItemConverterParameters : obj[] with get, set
  member ItemConverterType : Type with get, set
  member ItemIsReference : bool with get, set
  member ItemReferenceLoopHandling : ReferenceLoopHandling with get, set
  member ItemTypeNameHandling : TypeNameHandling with get, set
  member NamingStrategyParameters : obj[] with get, set
  member NamingStrategyType : Type with get, set
  ...

Full name: Newtonsoft.Json.JsonPropertyAttribute

--------------------
JsonPropertyAttribute() : unit
JsonPropertyAttribute(propertyName: string) : unit
Page.Id: string
Page.WebLogId: string
Page.AuthorId: string
Page.Title: string
Page.Permalink: string
Page.PublishedOn: int64
Page.UpdatedOn: int64
Page.ShowInPageList: bool
type bool = System.Boolean

Full name: Microsoft.FSharp.Core.bool
Page.Text: string
Page.Revisions: Revision list
type Revision =
  {AsOf: int64;
   Text: string;}
  static member Empty : Revision

Full name: Tres.Entities.Revision
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
static member Page.Empty : Page

Full name: Tres.Entities.Page.Empty
property Page.Empty: Page
Fork me on GitHub