type City = string

Full name: Index.Alias.City
Multiple items
val string : value:'T -> string

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

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

Full name: Microsoft.FSharp.Core.string
Multiple items
union case City.City: string -> City

--------------------
type City = | City of string

Full name: Index.SingleCase.City
val city : City

Full name: Index.SingleCase.city
val cityName : City -> string

Full name: Index.SingleCase.cityName
val name : string
val result : string

Full name: Index.SingleCase.result
type City = | CityName of string

Full name: Index.BreakDown.City
union case City.CityName: string -> City
val city : City

Full name: Index.BreakDown.city
val cityName : input:City -> string

Full name: Index.BreakDown.cityName
val input : City
val x : string
val result : string

Full name: Index.BreakDown.result
type ILocatable =
  interface
    abstract member Latitude : float
    abstract member Longitude : float
  end

Full name: Index.ILocatable
abstract member ILocatable.Latitude : float

Full name: Index.ILocatable.Latitude
Multiple items
val float : value:'T -> float (requires member op_Explicit)

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

--------------------
type float = System.Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
abstract member ILocatable.Longitude : float

Full name: Index.ILocatable.Longitude
type Place =
  {Name: string;
   Latitude: float;
   Longitude: float;}
  interface ILocatable

Full name: Index.FirstTry.Place
Place.Name: string
Place.Latitude: float
Place.Longitude: float
val this : Place
override Place.Latitude : float

Full name: Index.FirstTry.Place.Latitude
override Place.Longitude : float

Full name: Index.FirstTry.Place.Longitude
type Place =
  {Name: string;
   Latitude: float option;
   Longitude: float option;}
  member GetLocation : unit -> ILocatable option

Full name: Index.OptionalLocation.Place
Place.Latitude: float option
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
Place.Longitude: float option
member Place.GetLocation : unit -> ILocatable option

Full name: Index.OptionalLocation.Place.GetLocation
union case Option.Some: Value: 'T -> Option<'T>
val lat : float
val lng : float
val this : ILocatable
property ILocatable.Latitude: float
property ILocatable.Longitude: float
union case Option.None: Option<'T>
val houston : Place

Full name: Index.OptionalLocation.houston
member Place.GetLocation : unit -> ILocatable option
val camelot : Place

Full name: Index.OptionalLocation.camelot
type Location =
  {Latitude: float;
   Longitude: float;}

Full name: Index.Revised.Location
Location.Latitude: float
Location.Longitude: float
type Place =
  {Name: string;
   Location: Location option;}

Full name: Index.Revised.Place
Multiple items
Place.Location: Location option

--------------------
type Location =
  {Latitude: float;
   Longitude: float;}

Full name: Index.Revised.Location
val houston : Place

Full name: Index.Revised.houston
Place.Location: Location option
val camelot : Place

Full name: Index.Revised.camelot
Multiple items
type MeasureAttribute =
  inherit Attribute
  new : unit -> MeasureAttribute

Full name: Microsoft.FSharp.Core.MeasureAttribute

--------------------
new : unit -> MeasureAttribute
[<Measure>]
type degLat

Full name: Index.degLat
[<Measure>]
type degLng

Full name: Index.degLng
val degreesLatitude : x:float<'u> -> float<'u degLat>

Full name: Index.degreesLatitude
val x : float<'u>
val degreesLongitude : x:float<'u> -> float<'u degLng>

Full name: Index.degreesLongitude
val degLatResult : float<degLat>

Full name: Index.degLatResult
val degLngResult : float<degLng>

Full name: Index.degLngResult
module SingleCase

from Index
type Location =
  {Latitude: float<degLat>;
   Longitude: float<degLng>;}

Full name: Index.UnitsOfMeasure.Location
Location.Latitude: float<degLat>
Location.Longitude: float<degLng>
type Place =
  {Name: City;
   Location: Location option;}

Full name: Index.UnitsOfMeasure.Place
Place.Name: City
Multiple items
Place.Location: Location option

--------------------
type Location =
  {Latitude: float<degLat>;
   Longitude: float<degLng>;}

Full name: Index.UnitsOfMeasure.Location
Multiple items
union case City.City: name: string -> City

--------------------
type City =
  | City of name: string
  static member Create : name:string -> City

Full name: Index.City
static member City.Create : name:string -> City

Full name: Index.City.Create
val invalidArg : argumentName:string -> message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.invalidArg
type Location =
  internal {latitude: float<degLat>;
            longitude: float<degLng>;}
  member Latitude : float<degLat>
  member Longitude : float<degLng>
  static member Create : lat:float<degLat> * lng:float<degLng> -> Location

Full name: Index.Location
Location.latitude: float<degLat>
Location.longitude: float<degLng>
val this : Location
member Location.Latitude : float<degLat>

Full name: Index.Location.Latitude
member Location.Longitude : float<degLng>

Full name: Index.Location.Longitude
static member Location.Create : lat:float<degLat> * lng:float<degLng> -> Location

Full name: Index.Location.Create
val lat : float<degLat>
val lng : float<degLng>
type Place =
  {Name: City;
   Location: Location option;}

Full name: Index.Place
Multiple items
Place.Location: Location option

--------------------
type Location =
  internal {latitude: float<degLat>;
            longitude: float<degLng>;}
  member Latitude : float<degLat>
  member Longitude : float<degLng>
  static member Create : lat:float<degLat> * lng:float<degLng> -> Location

Full name: Index.Location
[<Measure>]
type m

Full name: Index.m
[<Measure>]
type ft

Full name: Index.ft
[<Measure>]
type mi

Full name: Index.mi
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
Multiple items
namespace FSharp.Data

--------------------
namespace Microsoft.FSharp.Data
Multiple items
type LiteralAttribute =
  inherit Attribute
  new : unit -> LiteralAttribute

Full name: Microsoft.FSharp.Core.LiteralAttribute

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

Full name: Index.ConnStr
type GetCityLocation = SqlCommandProvider<...>

Full name: Index.GetCityLocation
type SqlCommandProvider

Full name: FSharp.Data.SqlCommandProvider



<summary>Typed representation of a T-SQL statement to execute against a SQL Server database.</summary>
<param name='CommandText'>Transact-SQL statement to execute at the data source.</param>
<param name='ConnectionStringOrName'>String used to open a SQL Server database or the name of the connection string in the configuration file in the form of “name=&lt;connection string name&gt;”.</param>
<param name='ResultType'>A value that defines structure of result: Records, Tuples, DataTable, or SqlDataReader.</param>
<param name='SingleRow'>If set the query is expected to return a single row of the result set. See MSDN documentation for details on CommandBehavior.SingleRow.</param>
<param name='ConfigFile'>The name of the configuration file that’s used for connection strings at DESIGN-TIME. The default value is app.config or web.config.</param>
<param name='AllParametersOptional'>If set all parameters become optional. NULL input values must be handled inside T-SQL.</param>
<param name='ResolutionFolder'>A folder to be used to resolve relative file paths to *.sql script files at compile time. The default value is the folder that contains the project or script.</param>
<param name='DataDirectory'>The name of the data directory that replaces |DataDirectory| in connection strings. The default value is the project or script directory.</param>
type LookupLocations = City * City -> Place * Place

Full name: Index.LookupLocations
type TryFindDistance = Place * Place -> float<m> option

Full name: Index.TryFindDistance
type MetersToFeet = float<m> -> float<ft>

Full name: Index.MetersToFeet
type Show = Place * Place * float<ft> option -> string

Full name: Index.Show
val lookupLocation : City -> Place

Full name: Index.lookupLocation
val cmd : GetCityLocation
val result : Option<SqlCommandProvider<...>.Record>
SqlCommandProvider<...>.Execute(city: string) : Option<SqlCommandProvider<...>.Record>
val p : SqlCommandProvider<...>.Record
property SqlCommandProvider<...>.Record.Latitude: Option<float>
property SqlCommandProvider<...>.Record.Longitude: Option<float>
static member City.Create : name:string -> City
property SqlCommandProvider<...>.Record.City: string
static member Location.Create : lat:float<degLat> * lng:float<degLng> -> Location
val lookupLocations : start:City * dest:City -> Place * Place

Full name: Index.lookupLocations
val start : City
val dest : City
namespace System
namespace System.Device
namespace System.Device.Location
val toGeoCoordinate : _arg1:Location -> GeoCoordinate

Full name: Index.toGeoCoordinate
Multiple items
type GeoCoordinate =
  new : unit -> GeoCoordinate + 3 overloads
  member Altitude : float with get, set
  member Course : float with get, set
  member Equals : obj:obj -> bool + 1 overload
  member GetDistanceTo : other:GeoCoordinate -> float
  member GetHashCode : unit -> int
  member HorizontalAccuracy : float with get, set
  member IsUnknown : bool
  member Latitude : float with get, set
  member Longitude : float with get, set
  ...

Full name: System.Device.Location.GeoCoordinate

--------------------
GeoCoordinate() : unit
GeoCoordinate(latitude: float, longitude: float) : unit
GeoCoordinate(latitude: float, longitude: float, altitude: float) : unit
GeoCoordinate(latitude: float, longitude: float, altitude: float, horizontalAccuracy: float, verticalAccuracy: float, speed: float, course: float) : unit
val findDistance : start:Location * dest:Location -> float<m>

Full name: Index.findDistance
val start : Location
val dest : Location
val tryFindDistance : Place * Place -> float<m> option

Full name: Index.tryFindDistance
val metersToFeet : input:float<m> -> float<ft>

Full name: Index.metersToFeet
val input : float<m>
val feetToMiles : input:float<ft> -> float<mi>

Full name: Index.feetToMiles
val input : float<ft>
val workflow : (City * City -> float<ft> option)

Full name: Index.workflow
module Option

from Microsoft.FSharp.Core
val map : mapping:('T -> 'U) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.map
val serializePlace : _arg1:Place -> string

Full name: Index.serializePlace
val loc : Location
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
property Location.Latitude: float<degLat>
property Location.Longitude: float<degLng>
val serializeResult : place1:Place -> place2:Place -> distance:float<'u> option -> string

Full name: Index.serializeResult
val place1 : Place
val place2 : Place
val distance : float<'u> option
val place1' : string
val place2' : string
val d : float<'u>
val receiveInput : start:City * dest:City -> string

Full name: Index.receiveInput
val start' : Place
val dest' : Place
val calculateDistance : start:Place * dest:Place -> string

Full name: Index.calculateDistance
val start : Place
val dest : Place
val distance : float<m>
val showPlacesWithDistanceInMeters : start:Place * dest:Place * distance:float<m> -> string

Full name: Index.showPlacesWithDistanceInMeters
val showPlaces : start:Place * dest:Place * distance:float<ft> option -> string

Full name: Index.showPlaces
val distance : float<ft> option
type Stage =
  | AwaitingInput
  | InputReceived of start: City * dest: City
  | Located of start: Place * dest: Place
  | Calculated of Place * Place * float<m> option
  | Show of Place * Place * float<ft> option

Full name: Index.Stage
union case Stage.AwaitingInput: Stage
union case Stage.InputReceived: start: City * dest: City -> Stage
union case Stage.Located: start: Place * dest: Place -> Stage
union case Stage.Calculated: Place * Place * float<m> option -> Stage
Multiple items
union case Stage.Show: Place * Place * float<ft> option -> Stage

--------------------
type Show = Place * Place * float<ft> option -> string

Full name: Index.Show
type RunWorkflow = Stage -> string

Full name: Index.RunWorkflow
val runWorkflow : _arg1:Stage -> string

Full name: Index.runWorkflow
val distance : float<m> option
namespace System.Text
namespace Arachne
module Language

from Arachne
namespace Arachne.Uri
namespace Arachne.Http
module Cors

from Arachne.Http
module Template

from Arachne.Uri
namespace Freya
namespace Freya.Core
Multiple items
module Freya

from Freya.Core

--------------------
namespace Freya

--------------------
type Freya<'T> = FreyaState -> Async<'T * FreyaState>

Full name: Freya.Core.Types.Freya<_>
module Operators

from Freya.Core
namespace Freya.Lenses
namespace Freya.Lenses.Http
namespace Freya.Lenses.Http.Cors
namespace Freya.Machine
namespace Freya.Machine.Extensions
namespace Freya.Machine.Extensions.Http
namespace Freya.Machine.Extensions.Http.Cors
namespace Freya.Machine.Router
namespace Freya.Router
val represent : x:string -> Representation

Full name: Index.represent
Multiple items
val string : value:'T -> string

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

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
type Description =
  {Charset: Charset option;
   Encodings: ContentCoding list option;
   MediaType: MediaType option;
   Languages: LanguageTag list option;}

Full name: Freya.Machine.Extensions.Http.Types.Description
Multiple items
union case Charset.Charset: string -> Charset

--------------------
module Charset

from Arachne.Http

--------------------
type Charset =
  | Charset of string
  static member Iso88591 : Charset
  static member Unicode : Charset
  static member Utf8 : Charset

Full name: Arachne.Http.Charset
property Charset.Utf8: Charset
Multiple items
union case MediaType.MediaType: Type * SubType * Parameters -> MediaType

--------------------
module MediaType

from Arachne.Http

--------------------
type MediaType =
  | MediaType of Type * SubType * Parameters
  override ToString : unit -> string
  static member Css : MediaType
  static member Html : MediaType
  static member JavaScript : MediaType
  static member Json : MediaType
  static member Mapping : Mapping<MediaType>
  static member Text : MediaType
  static member Xml : MediaType
  static member format : (MediaType -> string)
  static member parameters_ : (MediaType -> Parameters) * (Parameters -> MediaType -> MediaType)
  static member parse : (string -> MediaType)
  static member subType_ : (MediaType -> SubType) * (SubType -> MediaType -> MediaType)
  static member tryParse : (string -> Choice<MediaType,string>)
  static member type_ : (MediaType -> Type) * (Type -> MediaType -> MediaType)

Full name: Arachne.Http.MediaType
property MediaType.Json: MediaType
Multiple items
union case LanguageTag.LanguageTag: Language * Script option * Region option * Variant -> LanguageTag

--------------------
type LanguageTag =
  | LanguageTag of Language * Script option * Region option * Variant
  override ToString : unit -> string
  static member Mapping : Mapping<LanguageTag>
  static member format : (LanguageTag -> string)
  static member parse : (string -> LanguageTag)
  static member tryParse : (string -> Choice<LanguageTag,string>)

Full name: Arachne.Language.LanguageTag
property LanguageTag.parse: string -> LanguageTag
Multiple items
namespace System.Data

--------------------
namespace Microsoft.FSharp.Data
type Encoding =
  member BodyName : string
  member Clone : unit -> obj
  member CodePage : int
  member DecoderFallback : DecoderFallback with get, set
  member EncoderFallback : EncoderFallback with get, set
  member EncodingName : string
  member Equals : value:obj -> bool
  member GetByteCount : chars:char[] -> int + 3 overloads
  member GetBytes : chars:char[] -> byte[] + 5 overloads
  member GetCharCount : bytes:byte[] -> int + 2 overloads
  ...

Full name: System.Text.Encoding
property Encoding.UTF8: Encoding
Encoding.GetBytes(s: string) : byte []
Encoding.GetBytes(chars: char []) : byte []
Encoding.GetBytes(chars: char [], index: int, count: int) : byte []
Encoding.GetBytes(chars: nativeptr<char>, charCount: int, bytes: nativeptr<byte>, byteCount: int) : int
Encoding.GetBytes(s: string, charIndex: int, charCount: int, bytes: byte [], byteIndex: int) : int
Encoding.GetBytes(chars: char [], charIndex: int, charCount: int, bytes: byte [], byteIndex: int) : int
val urlDecode : x:'a -> 'b

Full name: Index.urlDecode
val x : 'a
namespace System.Net
type WebUtility =
  static member HtmlDecode : value:string -> string + 1 overload
  static member HtmlEncode : value:string -> string + 1 overload

Full name: System.Net.WebUtility
val cities : Freya<City * City>

Full name: Index.cities
val freya : FreyaBuilder

Full name: Freya.Core.Syntax.freya
val qs : Query
module Optic

from Freya.Core.Freya
val get : o:'c -> Freya<'d> (requires member ( ^. ))

Full name: Freya.Core.Freya.Optic.get
Multiple items
module Request

from Freya.Lenses.Http.Cors.Lenses

--------------------
module Request

from Freya.Lenses.Http.Lenses
val query_ : Aether.Lens<FreyaState,Query>

Full name: Freya.Lenses.Http.Lenses.Request.query_
val start : string
val dest : string
val fst : tuple:('T1 * 'T2) -> 'T1

Full name: Microsoft.FSharp.Core.Operators.fst
Multiple items
union case OperatorLevel3.Query: OperatorLevel3

--------------------
type Query =
  | Query of string
  override ToString : unit -> string
  static member private Predicate : i:int -> bool
  static member Mapping : Mapping<Query>
  static member decoded_ : (Query -> string) * (string -> Query)
  static member format : (Query -> string)
  static member pairs_ : (Query -> (string * string option) list option) * ((string * string option) list -> Query)
  static member parse : (string -> Query)
  static member raw_ : (Query -> string) * (string -> Query)
  static member tryParse : (string -> Choice<Query,string>)

Full name: Arachne.Uri.Query
property Query.pairs_: (Query -> (string * string option) list option) * ((string * string option) list -> Query)
val get : Freya<string>

Full name: Index.get
val cities : City * City
val stage : Stage
val memo : m:Freya<'a> -> Freya<'a>

Full name: Freya.Core.Freya.memo
val getHandler : 'a -> Freya<Representation>

Full name: Index.getHandler
val json : string
val mediaTypes : MediaType list

Full name: Index.mediaTypes
property MediaType.Html: MediaType
property MediaType.Css: MediaType
property MediaType.JavaScript: MediaType
val common : FreyaMachine

Full name: Index.common
val freyaMachine : FreyaMachineBuilder

Full name: Freya.Machine.Syntax.freyaMachine
custom operation: using (FreyaMachineExtension)

Calls FreyaMachineBuilder.Using
val http : FreyaMachineExtension

Full name: Freya.Machine.Extensions.Http.Extension.http
val httpCors : FreyaMachineExtension

Full name: Freya.Machine.Extensions.Http.Cors.Extension.httpCors
custom operation: charsetsSupported ('a)

Calls FreyaMachineBuilder.CharsetsSupported
custom operation: corsHeadersSupported ('a)

Calls FreyaMachineBuilder.CorsHeadersSupported
custom operation: corsOriginsSupported ('a)

Calls FreyaMachineBuilder.CorsOriginsSupported
Multiple items
module AccessControlAllowOriginRange

from Freya.Machine.Extensions.Http.Cors.Syntax

--------------------
type AccessControlAllowOriginRange =
  | Origins of OriginListOrNull
  | Any

Full name: Arachne.Http.Cors.AccessControlAllowOriginRange
union case AccessControlAllowOriginRange.Any: AccessControlAllowOriginRange
custom operation: languagesSupported ('a)

Calls FreyaMachineBuilder.LanguagesSupported
custom operation: mediaTypesSupported ('a)

Calls FreyaMachineBuilder.MediaTypesSupported
val distanceCalculator : FreyaMachine

Full name: Index.distanceCalculator
custom operation: including (FreyaMachine)

Calls FreyaMachineBuilder.Including
custom operation: corsMethodsSupported ('a)

Calls FreyaMachineBuilder.CorsMethodsSupported
union case Method.GET: Method
union case Method.OPTIONS: Method
custom operation: methodsSupported ('a)

Calls FreyaMachineBuilder.MethodsSupported
custom operation: handleOk ('a)

Calls FreyaMachineBuilder.HandleOk
val app : FreyaRouter

Full name: Index.app
val freyaRouter : FreyaRouterBuilder

Full name: Freya.Router.Syntax.freyaRouter
custom operation: resource ('a) ('b)

Calls FreyaRouterBuilder.Resource

Domain Modeling with Types

Ryan Riley

Tachyus logo

@panesofglass

github/panesofglass

OWIN

Community for F# logo

Objective

Visualize distance between two cities by their geographic coordinates.

Process

1: 
2: 
3: 
4: 
(City "Houston, TX", City "San Mateo, CA")
|> lookupLocations
|> tryFindDistance
|> Option.map metersToFeet

Goal 1

Define City

Simplest thing possible (C#)

1: 
using City = string;

Simplest thing possible (F#)

1: 
2: 
module Alias =
    type City = string

Does this really help?

Can we do better?

Single-cased union types

1: 
2: 
module SingleCase =
    type City = City of string

Extract the value via pattern matching:

1: 
2: 
3: 
4: 
    let city = City "Houston, TX"
    let cityName (City name) = name
    let result = cityName city
    // > "Houston, TX"

"Long-form" to better explain:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
module BreakDown =
    type City =
        | CityName of string

    let city : City =
        CityName "Houston, TX"

    let cityName (input:City) =
        match input with CityName x -> x

    let result = cityName city
    //> "Houston, TX"

Goal 2

Define a Location type

Place type (C#)

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
public interface ILocatable {
    float Latitude { get; }
    float Longitude { get; }
}

public class Place : ILocatable {
    public string Name { get; set; }
    public float Latitude { get; set; }
    public float Longitude { get; set; }
}

Place type (F#)

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
type ILocatable =
    abstract Latitude : float
    abstract Longitude : float

module FirstTry =

    type Place =
        { Name : string
          Latitude : float
          Longitude : float }
        interface ILocatable with
            member this.Latitude = this.Latitude
            member this.Longitude = this.Longitude

What could go wrong?

  1. Latitude or longitude not provided
  2. Latitude and longitude mixed up
  3. Invalid city and location values

1. Handling missing values

Examples:

  • Atlantis
  • Camelot

C# version:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
class Locatable : ILocatable {
    public float Latitude { get; set; }
    public float Longitude { get; set; }
}

public class Place {
    public string Name { get; set; }
    public float? Latitude { get; set; }
    public float? Longitude { get; set; }
    public ILocatable GetLocation() {
        if (Latitude.HasValue && Longitude.HasValue) {
            return new Locatable {
                Latitude = Latitude.Value,
                Longitude = Longitude.Value
            };
        } else {
            return null; // Oh noes!
        }
    }
}

Usage:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
var houston = new Place
{
    Name = "Houston, TX",
    Latitude = 29.75293,
    // Oops! C# doesn't require all props
    //Longitude = -95.34876
};
houston.GetLocation();
//> null

Usage for a fictional place:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
var camelot = new Place
{
    Name = "Camelot",
    // Convenient to not have to specify values
};
camelot.GetLocation();
//> null

F# Place with optional ILocatable

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
module OptionalLocation =

    type Place =
        { Name : string
          Latitude : float option
          Longitude : float option }
        member this.GetLocation() : ILocatable option =
            match this.Latitude, this.Longitude with
            | Some lat, Some lng ->
                { new ILocatable with
                    member this.Latitude = lat
                    member this.Longitude = lng
                } |> Some
            | _ -> None

Usage:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
    let houston : Place =
        {
            Name = "Houston, TX"
            Latitude = Some 29.75293
            // must provide a value
            Longitude = Some -95.34876
        }
    houston.GetLocation()
    //> Some ILocatable...

Usage for fictional place:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
    let camelot : Place =
        {
            Name = "Camelot"
            // must provide values always
            Latitude = None
            Longitude = None
        }
    camelot.GetLocation()
    //> None

Yuck, right?

Can we do better?

Rethinking ILocatable

"Is-a" vs. "Has-a"

Latitude and Longitude belong together

Refactored Place type (C#)

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
public class Location {
    public float Latitude { get; set; }
    public float Longitude { get; set; }
}

public class Place {
    public string Name { get; set; }
    public Location Location { get; set; }
}

Usage:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
var houston = new Place
{
    Name = "Houston, TX",
    Location = new Location
    {
        Latitude = 29.75293,
        // Oops! C# doesn't require all props
        //Longitude = -95.34876
    }
};
houston.Location
//> Non-null Location with null field that is invalid!

Usage for a fictional place:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
var camelot = new Place
{
    Name = "Camelot",
    Location = null
};
camelot.Location
//> null

Note: "optional" indicated by a null

Refactored Place type (F#)

1: 
2: 
3: 
4: 
5: 
module Revised =

    type Location = { Latitude : float; Longitude : float }

    type Place = { Name : string; Location : Location option }

Usage:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
    let houston : Place =
        {
            Name = "Houston, TX"
            Location =
                Some {
                    Latitude = 29.75293
                    // Requires a value
                    Longitude = -95.34876
                }
        }
    houston.Location
    //> { Name = "Houston, TX"; Location = Some ... }

Usage for a fictional place:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
    let camelot : Place =
        {
            Name = "Camelot"
            Location = None
        }
    camelot.Location
    //> None

Note: No null

Has-a preferred to Is-a

"Composition over inheritance"

2. Mixing up latitude and longitude

Sadly we will have to leave C# behind

Units of measure

1: 
2: 
3: 
4: 
5: 
[<Measure>] type degLat
[<Measure>] type degLng

let degreesLatitude x = x * 1.<degLat>
let degreesLongitude x = x * 1.<degLng>

Correct Location

1: 
2: 
3: 
4: 
5: 
6: 
    type Location = {
        Latitude : float<degLat>
        Longitude : float<degLng>
    }

    type Place = { Name : City; Location : Location option }

Make invalid values impossible

Types can only get you so far

  1. City allows null and ""
  2. Location allows latitude < -90 and > 90
  3. Location allows longitude < -180 and > 180

3. Valid values

Revisiting City

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
type City = City of name : string
    with
    static member Create (name : string) =
        match name with
        | null | "" ->
            invalidArg "Invalid city"
                "The city name cannot be null or empty."
        | x -> City x

Better than nothing

Revisiting Location

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
type Location =
    internal { latitude : float<degLat>; longitude : float<degLng> }
    member this.Latitude = this.latitude
    member this.Longitude = this.longitude
    static member Create (lat, lng) =
        if -90.<degLat> > lat || lat > 90.<degLat> then
            invalidArg "lat"
                "Latitude must be within the range -90 to 90."
        elif -180.<degLng> > lng && lng > 180.<degLng> then
            invalidArg "lng"
                "Longitude must be within the range -180 to 180."
        else { latitude = lat; longitude = lng }

Revisiting Place

1: 
type Place = { Name : City; Location : Location option }

Place still has yet to change

Single Responsibility Principle

Further study

Goal 3: Processing Requests

Process

1: 
2: 
3: 
4: 
(City "Houston, TX", City "San Mateo, CA")
|> lookupLocations
|> tryFindDistance
|> Option.map metersToFeet

How do we translate?

  • UI input: two cities
  • Calculation input: two geographic coordinates
  • Unit conversion: meters -> feet

Start with types

1: 
2: 
3: 
4: 
5: 
6: 
7: 
type LookupLocations = City * City -> Place * Place

type TryFindDistance = Place * Place -> float<m> option

type MetersToFeet = float<m> -> float<ft>

type Show = Place * Place * float<ft> option -> string

Implementation

Retrieving values from other sources

FSharp.Data.SqlClient

Type-checked SQL

1: 
2: 
3: 
4: 
5: 
type GetCityLocation = SqlCommandProvider<"
    SELECT City, Latitude, Longitude
    FROM [dbo].[CityLocations]
    WHERE City = @city
    ", ConnStr, SingleRow = true>

Lookup coordinates by city name

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
let lookupLocation (City name) =
    use cmd = new GetCityLocation()
    let result = cmd.Execute(name)
    match result with
    | Some p ->
        match p.Latitude, p.Longitude with
        | Some lat, Some lng ->
            { Name = City.Create(p.City)
              Location = Location.Create(
                            degreesLatitude lat,
                            degreesLongitude lng) |> Some }
        | _, _ ->
            { Name = City.Create(p.City); Location = None }
    | None -> { Name = City.Create(name); Location = None }

let lookupLocations (start, dest) =
    lookupLocation start, lookupLocation dest

Calculate Distance

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
open System.Device.Location
let toGeoCoordinate = function
    { latitude = lat; longitude = lng } ->
        GeoCoordinate(lat / 1.<degLat>, lng / 1.<degLng>)

let findDistance (start: Location, dest: Location) : float<m> =
    (start |> toGeoCoordinate).GetDistanceTo(dest |> toGeoCoordinate)
    * 1.<m>

let tryFindDistance : TryFindDistance = function
    | { Location = Some start }, { Location = Some dest } ->
        findDistance (start, dest) |> Some
    | _, _ -> None

Unit conversion

1: 
2: 
[<Measure>] type m
[<Measure>] type ft
1: 
2: 
let metersToFeet (input: float<m>) =
    input * 3.2808399<ft/m>

Wrapping up the steps

1: 
2: 
3: 
4: 
let workflow =
    lookupLocations
    >> tryFindDistance
    >> Option.map metersToFeet

What about Show?

Goal 4: Visualizing Results

What do we need to do?

  1. Serialize data for display in a browser
  2. Define HTTP resources to process requests
  3. Render the UI

1. Serialization

Serialized data should also send Places

Bridging the gap

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
let rec receiveInput(start, dest) =
    let start', dest' = lookupLocations(start, dest)
    calculateDistance(start', dest')
and calculateDistance(start, dest) =
    match tryFindDistance(start, dest) with
    | Some distance ->
        showPlacesWithDistanceInMeters(start, dest, distance)
    | None -> showPlaces(start, dest, None)
and showPlacesWithDistanceInMeters(start, dest, distance) =
    showPlaces(start, dest, Some(metersToFeet distance))
and showPlaces(start, dest, distance) =
    serializeResult start dest distance

Defunctionalization

Defining state machine Stages

1: 
2: 
3: 
4: 
5: 
6: 
type Stage =
    | AwaitingInput
    | InputReceived of start : City * dest : City
    | Located of start : Place * dest : Place
    | Calculated of Place * Place * float<m> option
    | Show of Place * Place * float<ft> option

Process Stages recursively

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
type RunWorkflow = Stage -> string

let rec runWorkflow = function
    | AwaitingInput -> runWorkflow AwaitingInput
    | InputReceived(start, dest) ->
        runWorkflow (Located(lookupLocations(start, dest)))
    | Located(start, dest) ->
        runWorkflow (Calculated(start, dest, tryFindDistance(start, dest)))
    | Calculated(start, dest, distance) ->
        runWorkflow (Show(start, dest, distance |> Option.map metersToFeet))
    | Show(start, dest, distance) -> serializeResult start dest distance

Output: JSON

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
{
    "start": {
        "name": "Houston, TX",
        "location": {
            "latitude": 29.760427,
            "longitude": -95.369803
        }
    },
    "dest": {
        "name": "San Mateo, CA",
        "location": {
            "latitude": 31.700148,
            "longitude": -106.275785
        }
    },
    "distance": 3493771.738857
}

2. Define HTTP resources

  • MVC
  • Web API
  • ?

Is it possible to model HTTP itself?

This is HTTP

HTTP State Machine

Freya

Freya logo

Freya Machines = HTTP Resources

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
let distanceCalculator =
    freyaMachine {
        including common
        corsMethodsSupported [GET; OPTIONS]
        methodsSupported [GET; OPTIONS]
        handleOk getHandler }

let app =
    freyaRouter {
        resource "/calc{?start,dest}" distanceCalculator
    }

Machine Composition!

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
let mediaTypes =
    [ MediaType.Html; MediaType.Json
      MediaType.Css; MediaType.JavaScript]
let common =
    freyaMachine {
        using http
        using httpCors
        charsetsSupported Charset.Utf8
        corsHeadersSupported [ "accept"; "content-type" ]
        corsOriginsSupported AccessControlAllowOriginRange.Any
        languagesSupported (LanguageTag.parse "en")
        mediaTypesSupported mediaTypes }

That's nice, but how do we map the domain?

Parse the query string for cities

1: 
2: 
3: 
4: 
5: 
6: 
let cities = freya {
    let! qs = Freya.Optic.get Request.query_
    let (Some [ "start", Some start
                "dest",  Some dest ]) = qs |> fst Query.pairs_
    return City.Create(urlDecode start),
           City.Create(urlDecode dest) }

Define the GET handler

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let get = freya {
    let! cities = cities
    let stage = InputReceived cities
    return runWorkflow stage } |> Freya.memo

let getHandler _ = freya {
    let! json = get
    return represent json }

3. Show and Tell



Review

1. Make invalid states impossible

2. Provide types from external sources

3. Declare your app's state machine

4. Even "untyped" protocols can benefit from types

5. Types can't always get you all the way

Resources

Questions?