Multiple items
val Failure : message:string -> exn

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

--------------------
active recognizer Failure: exn -> string option

Full name: Microsoft.FSharp.Core.Operators.( |Failure|_| )
Multiple items
val string : value:'T -> string

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

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

Full name: Microsoft.FSharp.Core.string
union case Option.Some: Value: 'T -> Option<'T>
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val tail : list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.tail
union case Option.None: Option<'T>
Multiple items
type NoEqualityAttribute =
  inherit Attribute
  new : unit -> NoEqualityAttribute

Full name: Microsoft.FSharp.Core.NoEqualityAttribute

--------------------
new : unit -> NoEqualityAttribute
Multiple items
type NoComparisonAttribute =
  inherit Attribute
  new : unit -> NoComparisonAttribute

Full name: Microsoft.FSharp.Core.NoComparisonAttribute

--------------------
new : unit -> NoComparisonAttribute
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
val fst : tuple:('T1 * 'T2) -> 'T1

Full name: Microsoft.FSharp.Core.Operators.fst
module Option

from Microsoft.FSharp.Core
val bind : binder:('T -> 'U option) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.bind

Improve the signal, reduce the noise

bring F# to the table




@tjaskula

jaskula.fr

alt text

About me

This talk is about

Benefits of moving from OOP to FP for a startup project

I've always been an OOP developer

but...

The snake which cannot cast its skin has to die. As well the minds which are prevented from changing their opinions; they cease to be mind. Friedrich Nietzsche

Context

Building a startup it's not just about writing the code

Time spent on non technical tasks

  • Business plan
  • Market survey
  • Advertising
  • Legal
  • etc.

Time spent on technical tasks

Not directly related to coding activity

  • Hosting
  • Build and deployment processes
  • Integrating with external web APIs services
  • etc.

Time spent for coding must deliver real business value

Can we measure the part of business value inside our code ?

Stats has been made before

alt text *http://simontylercousins.net/does-the-language-you-use-make-a-difference-revisited/

alt text *http://simontylercousins.net/does-the-language-you-use-make-a-difference-revisited/

Introducing to SNR

Signal-to-Noise Ratio

Compares a level of desired signal to the level of background noise.

\(\mathrm{SNR} = \frac{\mu_\mathrm{sig}}{\sigma_\mathrm{bg}}\)

SNR relation to the code

SIGNAL = Information producing Business Value
NOISE = Everything else impeding the transfer of Information

Signal

"Signal is information which conveys knowledge via facts. Knowledge is derived from Facts. Fact is produced by Business Use Case in time"

Signal properties

  • Fact that happened incorporated in time
  • Fact is an Immutable Value
  • Decisions are made on facts (humans and machines)

Noise

"Everything which infers with a signal and not contribute to the Knowledge"

Noise properties

  • Irrelevant or false data
  • Fact mutation
  • Everything that hinders readability of Facts

Categories of code noises

  • Organization
  • Readability
  • Correctness
  • Expressiveness
  • Frameworks
  • Effort
  • Complexity

Theoretical metric

Applicable to each business use case

\(\mathrm{CodeSNR} = \frac{LoC_\mathrm{domainUseCase}}{LoC_\mathrm{total}}\times{100}\)

The bigger the CodeSNR is, the better it is

Unfortunately there is no automatic tool to measure it...

Categories of noises

Can be measured

  • Correctness
  • Expressiveness
  • Frameworks
  • Complexity

Categories of noises

Just a subjective estimation

  • Organization
  • Readability
  • Effort

Let's create a REST service

Registration process business rules:

  • All fields are required (email, password, privacy selection, provider)
  • Password should match the password policy
  • Email address should be unique
  • If user from trusted provider then email is automatically confirmed
  • If not, an activation code should be created and sent to the user's email address
  • Account is created and password is encrypted

ASP.NET Web Api 2

  • C# naive implementation
  • C# OOP implementation
  • F# implementation

Registration Use Case

alt text

Naive OOP implementation (Page 1)

[HttpPost]
[Route("api/register")]
public IHttpActionResult Register(RegisterRepresentation rep)
{
    var confirmationUrl = new Uri(Request.RequestUri, "/confirmation");

    if (ModelState.IsValid)
    {
        if (!Regex.IsMatch(rep.Password,
            @"(?!.*\s)[0-9a-zA-Z!@#\\$%*()_+^&amp;}{:;?.]*$"))
        {
            ModelState.AddModelError("password", "The password 
                                            does not match the policy");
            return BadRequest(ModelState);
        }

        IHttpActionResult response = Ok(); 

Naive OOP implementation (Page 2)

using (var ctx = new RegistrationContext())
{
    if (ctx.Accounts.Any(a => a.Email == rep.Email))
        return Conflict();

    var cryptographer = new Cryptographer();
    string cryptedPassword = cryptographer.GetPasswordHash(
        rep.Password, cryptographer.CreateSalt());

    var account = new Account
    {
        Email = rep.Email,
        Password = cryptedPassword,
        Provider = rep.Provider
    };
    // state mutation???
    account.ConfirmEmail(DateTime.Now);

Naive OOP implementation (Page 3)

if (!account.IsEmailConfirmed)
    {
        account.SetActivationCode(Guid.NewGuid(), DateTime.Now.AddDays(5));
        var notifier = new Notifier();
        notifier.SendActivationNotification(account.Email);

        response = Created(confirmationUrl, new ConfirmationRepresentation
        {
            Email = account.Email,
            Code = account.ActivationCode,
            ExpirationTime = account.ActivationCodeExpirationTime.Value
        });
    }
    ctx.Accounts.Add(account);
    ctx.SaveChanges();

    return response;
    }
  }
return BadRequest(ModelState);
}

Immediate feedback

alt text

Observations

  • Procedural code
  • State mutation
  • Decisions made by intermediate steps
  • Orchestrated by client
  • Cannot be tested

Naive OOP implementation noises

Lines of code: 399

  • Correctness: 26%
  • Expressiveness: 25%
  • Frameworks: 42%
  • Complexity: Max 6 - Average 1.56
  • Organization: Easy
  • Readability: Bad
  • Effort: High

    CodeSNR = 7%

We can do better

OOP should not be procedural. Let's introduce some abstractions

public interface IRepresentationValidator
{
    bool Validate(RegisterRepresentation representation);
}

public interface IRegistrationService
{
    bool ShouldConfirmSubscription(RegisterRepresentation rep);
    bool CanRegister(string accountEmail, 
                        Func<string, Account> canRegister);
    Account Register(RegisterRepresentation rep);
}

public interface INotifier
{
    void SendActivationNotification(string emailAddress);
}

public interface IAccountRepository
{
    Account FindByEmail(string email);
    void Save(Account account);
}

Refactored OOP implementation (Page 1)

[HttpPost]
[Route("api/register")]
public IHttpActionResult Register(RegisterRepresentation rep)
{
    var confirmUrl = new Uri(Request.RequestUri, "/confirm...");

    if (_validator.Validate(rep))
        ModelState.AddModelError("password", 
                "The password format does not match the policy");

    if (ModelState.IsValid)
    {
        if (!_registrationService.CanRegister(rep.Email,
                email => _accountRepository.FindByEmail(email)))
            return Conflict();

        IHttpActionResult response = Ok();
        // what if this is null ?
        var account = _registrationService.Register(rep);

Refactored OOP implementation (Page 2)

if (_registrationService.ShouldConfirmSubscription(rep))
        {
            account.SetActivationCode(Guid.NewGuid(), 
                                    DateTime.Now.AddDays(5));
            _notifier.SendActivationNotification(account.Email);

            response = Created(confirmUrl, 
                new ConfirmationRepresentation
                {
                    Email = account.Email,
                    Code = account.ActivationCode,
                    ExpirationTime = 
                        account.ActivationCodeExpirationTime.Value
                });
        }
        _accountRepository.Save(account);
        return response;
    }
    return BadRequest(ModelState);
}

Immediate feedback

alt text

Observations

  • Composition through dependencies and abstractions
  • State mutation
  • Decisions made by intermediate steps
  • Orchestrated by client
  • Testable
  • Lines of code explosion because of new "abstractions"

Refactored OOP implementation noises

Lines of code: 574 (+45%)

  • Correctness: 25% (+1%)
  • Expressiveness: 34% (+9%)
  • Frameworks: 42% (-6%)
  • Complexity: Max 5 - Average 1.56
  • Organization: Hard
  • Readability: Good
  • Effort: Medium

    CodeSNR = 5% (-2%)

Some noises

Language construction

public RegisterController(
        IAccountRepository accountRepository,
        IRepresentationValidator validator,
        IRegistrationService registrationService,
        INotifier notifier)
{
    if (accountRepository == null)
        throw new ArgumentNullException("accountRepository");
    if (validator == null)
        throw new ArgumentNullException("validator");
    if (registrationService == null)
        throw new ArgumentNullException("validator");
    if (notifier == null)
        throw new ArgumentNullException("notifier");

    _accountRepository = accountRepository;
    _validator = validator;
    _registrationService = registrationService;
    _notifier = notifier;
}

Some noises

  • Introduction of ORM framework
  • Introduction of DI framework
  • Introduction of Mappers between layers

Some noises

Interface map to implementation 1:1

alt text

Some noises

  • Reused Abstraction Principle violated

F# signal improvement

HTTP is a functional type

1: 
type Http = Request -> Response

Business Use Case as a function

1: 
let useCase = 'Input -> 'Fact

Http as function

1: 
2: 
3: 
4: 
5: 
6: 
type Http = Request<'Input> -> 

            // business use case
            ('Input -> 'Fact) ->

            Response<'Fact>

Error handling

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
type Result<'TSuccess,'TFailure> = 
    | Success of 'TSuccess
    | Failure of 'TFailure

type Error =
    | ValidationError of string
    | AccountExists of string
    | DatabaseError of string

*Railway Programming http://fsharpforfunandprofit.com/posts/recipe-part2/

Http as function with error handling

1: 
2: 
3: 
4: 
5: 
6: 
type Http = Request<'Input> -> 

            // business use case
            ('Input -> Result<'Fact, Error>) ->

            Response<_>

Business use case

  • validate input
  • invoke domain
  • persist
  • return result or error

Step 1 : Input validation

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
let passwordPolicy registerRepresentation =

  match registerRepresentation.Password with
    | Match @"(?!.*\s)[0-9a-zA-Z!@#\\$%*()_+$" _ -> 
                registerRepresentation |> Success
    | _ ->  ValidationError("The password format does 
                                    not match the policy") 
                                       |> Failure

//-----------------

val passwordPolicy : RegisterRepresentation 
                      -> Result<RegisterRepresentation, Error>

Active patterns

1: 
2: 
3: 
4: 
5: 
let (|Match|_|) regex str =
    let m = Regex(regex).Match(str)
    match m.Success with
      | true -> Some (List.tail [for g in m.Groups -> g.Value])
      | false -> None

Input validation composition

1: 
2: 
3: 
4: 
5: 
6: 
let validateAll =
    passwordPolicy &&& checkPrivacy

//-----------------
val validateAll : (RegisterRepresentation 
                      -> Result<RegisterRepresentation, Error>)
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let (&&&) v1 v2 = 
    let addSuccess r1 r2 = r1 // return first
    let addFailure s1 s2 = concatErrors [s1; s2]  // concat
    plus addSuccess addFailure v1 v2 

//-----------------
val v1: ('a -> Result<'b, Error>)
val v2: ('a -> Result<'c, Error>)

Registration use case

After step 1

1: 
2: 
3: 
4: 
5: 
let useCase = Validation.validateAll

//-----------------
(RegisterRepresentation ->
 Result<RegisterRepresentation, Error>)

Step 2 : Designing domain with types

Make illegal states unrepresentable

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
type EmailAddress = EmailAddress of string

type VerifiedEmailAddress = VerifiedEmail of EmailAddress

type EmailAddressContactInfo =
    | Unverified of EmailAddress
    | Verified of VerifiedEmailAddress

[<NoEquality;NoComparison>]
type Account =
    {
        Email : EmailAddressContactInfo
        Password : string
        Provider : string
        Confirmation : ConfirmationInfo option
    }

Designing with types

"It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures" -- Alan Perlis

Mapping to domain

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let normalizeEmail registerRepresentation =
{
    Account.Email = 
        registerRepresentation.Email.Trim().ToLower() 
                    |> EmailAddress |> Unverified
    Password = registerRepresentation.Password
    Provider = registerRepresentation.Provider
    Confirmation = None
}

Registration use case

After step 2

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let useCase = 
    Validation.validateAll
    >> map Validation.normalizeEmail

//-----------------
(RegisterRepresentation ->
    Result<Account,Domain.Error>)

Map function

1: 
2: 
3: 
4: 
let map f x =
    match x with
        | Success s -> Success(f s)
        | Failure f -> Failure f

Step 3 : Domain logic and types

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
let tryConfirmEmail account =
    match account.Email with
        | Unverified e when account.Provider = "OAuth" -> 
            {account with Email = e 
                |> VerifiedEmail 
                |> Verified}
                |> Success
        | Unverified _ -> account |> Success
        | Verified _ -> 
                "Email can't be verified at this step"
                 |> ValidationError
                 |> Failure

Registration use case

After step 3

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let useCase = 
    Validation.validateAll
    >> map Validation.normalizeEmail
    >> bind RegistrationService.tryConfirmEmail
    >> bind RegistrationService.setActivationCode

//-----------------
(RegisterRepresentation ->
    Result<Account, Error>)

Bind function

1: 
2: 
3: 
4: 
let bind f x =
    match x with
        | Success s -> f s
        | Failure f -> Failure f

Persisting with Type Providers

1: 
2: 
3: 
4: 
5: 
6: 
let persistRegistration =
    tryCatch (tee (save mapRegistration)) 
            (fun ex -> DatabaseError(ex.Message))

//-----------------
val persistRegistration: (Account -> Result<Account,Error>)

Returning a result from dead end

1: 
2: 
3: 
let tee f x =
    f x |> ignore
    x

Try catch

1: 
2: 
3: 
4: 
5: 
let tryCatch f exnHandler x =
    try
        f x |> Success
    with
    | ex -> exnHandler ex |> Failure

Persistance mapping with Type Providers

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
type dbSchema = SqlDataConnection<"...">

let save map input =

    let db = dbSchema.GetDataContext()

    let newRecord = map input

    db.AccountEntity.InsertOnSubmit(newRecord)
    db.DataContext.SubmitChanges()

let mapRegistration input =

    let newRecord = new dbSchema.ServiceTypes.AccountEntity()

    newRecord.Password <- input.Password
    newRecord.Provider <- input.Provider

    let email, isConfirmed = getEmail input
    newRecord.Email <- email
    newRecord.IsEmailConfirmed <- isConfirmed

    newRecord

Compose business use case

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let start =
        Validation.validateAll
        >> map Validation.normalizeEmail
        >> bind RegistrationService.tryConfirmEmail
        >> bind RegistrationService.setActivationCode
        >> bind Database.findByEmailRegistration
        >> bind Database.persistRegistration
        >> bind Notification.sendActivationEmail

Compose business use case'

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let start x =
    Validation.validateAll
    >> map Validation.normalizeEmail
    >=> RegistrationService.tryConfirmEmail
    >=> RegistrationService.setActivationCode
    >=> Database.findByEmailRegistration
    >=> Database.persistRegistration
    >=> Notification.sendActivationEmail

Bind infix operator

1: 
2: 
3: 
4: 
5: 
6: 
let (>>=) m f =
    bind f m

// kleisli operator
let (>=>) f1 f2 = 
    fun x -> f1 x >>= f2

Compose business use case''

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let start x =
    Validation.validateAll
    >!> Validation.normalizeEmail
    >=> RegistrationService.tryConfirmEmail
    >=> RegistrationService.setActivationCode
    >=> Database.findByEmailRegistration
    >=> Database.persistRegistration
    >=> Notification.sendActivationEmail

Map infix operator

1: 
2: 
3: 
4: 
5: 
let (<!>) m f = 
      map f m

let (>!>) f1 f2 = 
    fun x -> f1 x <!> f2

Register it within Web Api framework

1: 
2: 
3: 
4: 
5: 
6: 
type CustomControllerActivator() =
    interface IHttpControllerActivator with
        member x.Create(request, controllerDescriptor, 
                            controllerType) =
            new RegisterController(UseCases.Registration.start) 
                                            :> IHttpController

Handle it

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
[<HttpPost>]
[<Route("api/register")>]
member x.Register(representation : RegisterRepresentation) =
  let confirmUrl = Uri(x.Request.RequestUri, "/confirm...")
  match startProcess representation with
    | Success s -> 
        match s.Confirmation with
        | Some c -> 
            match c.Activation with
            | Some a -> x.Created(confirmUrl,
                        { 
                            Email = fst (getEmail s)
                            Code = a.ActivationCode 
                        }) :> IHttpActionResult
            | None -> x.Ok() :> IHttpActionResult
        | None -> x.Ok() :> IHttpActionResult
    | Failure f -> 
        match f with
        | ValidationError ve -> x.BadRequest(x.ModelState) 
                                :> IHttpActionResult
        | AccountExists _ -> x.Conflict() :> IHttpActionResult
        | DatabaseError de -> 
            x.InternalServerError(Exception(de)) 
                                :> IHttpActionResult

Handle it with computation expressions

 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: 
[<HttpPost>]
[<Route("api/register")>]
member x.Register2(representation : RegisterRepresentation) =
   let confirmUrl = Uri(x.Request.RequestUri, "/confir...")
   match startProcess representation with
    | Success s -> 
      let rsp = 
        maybeRes
        {
            let! confirmation = s |> (fun a -> a.Conf) 
            let! activation = confirmation 
                                |> (fun c -> c.Acti)
            let! r = (s, activation) |> (fun (ss, a) -> 
                    Some (getConf (fst (getEmail ss)) a))
            return r
        }
        match rsp with
        | Some r -> x.Created(confirmUrl, r) 
                            :> IHttpActionResult
        | None -> x.Ok() :> IHttpActionResult

    | Failure f -> 
        match f with
        | ValidationError ve -> x.BadRequest(x.ModelState) 
                                :> IHttpActionResult
        | AccountExists _ -> x.Conflict() 
                                :> IHttpActionResult
        | DatabaseError de -> 
                x.InternalServerError(Exception(de)) 
                                :> IHttpActionResult

Computation expression

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
type MaybeResBuilder() =

    member this.Bind(x, f) = Option.bind f x            

    member this.Return(x) =
        Some x

let maybeRes = new MaybeResBuilder()

Easy testing

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
[<Fact>]
let ``Post returns 'Ok' on valid data and trusted provider``() =

    let start _ = { Account.Email = "test@email.fr" 
                        |> EmailAddress 
                        |> VerifiedEmail 
                        |> Verified
                    Password = "pass"
                    Provider = "OAuth"
                    Confirmation = None} |> Success

    use controller = new RegisterController(start)
    controller.Request <- new HttpRequestMessage(HttpMethod.Post, 
                            "http://localhost/api/register")

    let representation = {Email = "test@email.fr"
                          Password ="pass"
                          Privacy = true
                          Provider ="OAuth"}

    let result : IHttpActionResult = controller.Register representation

    result |> should be ofExactType<Results.OkResult>

Immediate feedback

alt text

Observations

  • Composition through functions
  • Functions are also values
  • No state mutation
  • Business use case function
  • Testable

F# implementation noises

Lines of code: 397

  • Correctness: 5% (-20%)
  • Expressiveness: 10% (-24%)
  • Frameworks: 28% (-14%)
  • Complexity: N/A
  • Organization: Easy
  • Readability: Excellent
  • Effort: Low

    CodeSNR = 23% (+16%)

Code organization

alt text alt text

How we've improved the signal with F#

  • No need of frameworks for DI
  • No need of AOP frameworks
  • No need of ORM frameworks
  • No need to know 23 OOP patterns and more

OOP

"OOP promise REUSE = Big Lie" -- Rich Hickey

Values

  • Shared freely, no one can mess up
  • No locks, copy, clone easy to fabricate (testing, simulation)
  • Discourage imperative programming
  • Generic (map, list, sets)
  • Best interface
  • Can be composed and aggregated

Let's create a REST service

Can turn bad easily with OOP...

when not very carefully used...

Like many things in the real life after all

Let's saw this board

alt text

Let's install this air conditioner

alt text

Let's get this party started!

alt text

And finally

Let's get some liquor and guns, but without leaving our vehicle

alt text

Murphy's law

Anything that can go wrong, will go wrong.

References

One more thing

ncrafts.io Paris with full F# track from FSharpWorks

alt text

Thank you

@tjaskula