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: |
|
The F# version (within the namespace Tres.Entities
):
1: 2: 3: 4: 5: 6: |
|
The RequireQualifiedAccess
attribute means that this module cannot be open
ed, 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 struct
s, 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: |
|
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: |
|
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:
|
|
, 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:
|
|
. 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.
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<_>
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
Full name: Tres.Entities.Revision.Empty
type RequireQualifiedAccessAttribute =
inherit Attribute
new : unit -> RequireQualifiedAccessAttribute
Full name: Microsoft.FSharp.Core.RequireQualifiedAccessAttribute
--------------------
new : unit -> RequireQualifiedAccessAttribute
from Tres.Entities
type LiteralAttribute =
inherit Attribute
new : unit -> LiteralAttribute
Full name: Microsoft.FSharp.Core.LiteralAttribute
--------------------
new : unit -> LiteralAttribute
Full name: Tres.Entities.AuthorizationLevel.Administrator
Full name: Tres.Entities.AuthorizationLevel.User
{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
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
Full name: Microsoft.FSharp.Core.bool
{AsOf: int64;
Text: string;}
static member Empty : Revision
Full name: Tres.Entities.Revision
Full name: Microsoft.FSharp.Collections.list<_>
Full name: Tres.Entities.Page.Empty