val x : int

Full name: index.x
val y : int

Full name: index.y
Multiple items
val char : value:'T -> char (requires member op_Explicit)

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

--------------------
type char = System.Char

Full name: Microsoft.FSharp.Core.char
Multiple items
val int : value:'T -> int (requires member op_Explicit)

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

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
Multiple items
val int16 : value:'T -> int16 (requires member op_Explicit)

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

--------------------
type int16 = System.Int16

Full name: Microsoft.FSharp.Core.int16

--------------------
type int16<'Measure> = int16

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

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

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

Full name: Microsoft.FSharp.Core.string
module String

from Microsoft.FSharp.Core
Multiple items
val double : value:'T -> double (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.double

--------------------
type double = System.Double

Full name: Microsoft.FSharp.Core.double
type bool = System.Boolean

Full name: Microsoft.FSharp.Core.bool
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  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 id : x:'T -> 'T

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

from Microsoft.FSharp.Core
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
val enum : value:int32 -> 'U (requires enum)

Full name: Microsoft.FSharp.Core.Operators.enum
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>

Do you even type, Bro?

Who am I?

Youenn Bouglouan

C# by day, F# by night

Why today's topic?

Waterfall vs Agile

Java vs C#

PC vs Mac (vs Linux)

Object Oriented vs Functional

Weakly Typed vs Strongly Typed

Who is this presentation for?

Developers -> get a better idea of what's out there

QA Specialists -> understand why there are bugs and issues

Part I


Typing 101

What's a Type?

A way to represent data or behavior within a programming language

  • primitive types (int, bool, char, float...)
  • product types (classes, records, objects, tuples...)
  • sum types (unions but not only... we'll see those later!)
  • sets (lists, arrays, maps, dictionaries...)
  • functions(!)
  • interfaces(!)

What's a Type System?

For a given language:

  • defines the kind of types available
  • maps types to the various constructs (expressions, variables, functions... )
  • determines how types interact with each other
  • sets the rules that make a type either valid or invalid

Typing:

Dynamic vs Static

Dynamic

types are checked at runtime -> runtime errors

1: 
2: 
3: 
4: 
5: 
// Python
x = 10
y = x + 1 // Ok
z = x + "1" // Runtime error
// TypeError: unsupported operand type(s) for +: 'int' and 'str'

Static

Types are checked at compile time -> compilation errors

1: 
2: 
3: 
4: 
5: 
// F#
let x = 10
let y = x + 1 // Ok
let z = x + "1" // Compiler error
// The type 'string' does not match the type 'int'

Typing:

Weak vs Strong

Not the same as Dynamic vs Static!

Weak

type checking is not (very) strict

implicit conversions (usually)

little guarantees on the program's correctness

Weak typing in a dynamic language

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
// JavaScript
'1' + '2' // returns the string "12"
'1' - '2' // returns the number -1
'1' * '2' // returns the number 2
var myObject = { valueOf: function () { return 3 }} // myObject is an Object
'1' + myObject // returns a string: "13"
1 + myObject // returns a string: 4
[] + {} // returns an Object
{} + [] // returns the number 0... wait whaaaaaaaaaaaaaaaaaaaat?

Weak typing in a static language - 1

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
// C#
var x = 2 + 2.0; // x is a double with value 4
var y = 2.1 + "2"; // y is a string with value "2.12"

char c = 'a';
int i = c; // i is an integer with value 97

int x = 100000;
int y = (short) x; // y is an integer with value -31072 due to integer overflow

Weak typing in a static language - 2

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
// C#
public class Animal {}
public class Reptile : Animal {}
public class Mammal : Animal {}

public void Test(Animal a)
{
  var r = (Reptile) a;
}

Test(new Mammal());
// Run-time exception: Unable to cast object of type 'Mammal' to type 'Reptile'.

Strong

Type checking is strict(er)

program's correctness is easier to prove (well, in theory)

conversions must be explicit

Strong typing in a dynamic language

Back to our first example in Python!

1: 
2: 
3: 
4: 
5: 
// Python
x = 10
y = x + 1 // Ok
z = x + "1" // Runtime error
// TypeError: unsupported operand type(s) for +: 'int' and 'str'

Strong typing in a static language

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
// F#
let x = 2 + 2.0 // Compiler error, The type 'float' does not match the type 'int'
let y = 2.1 + "2" // Compiler error, The type 'string' does not match the type 'float'

let c: char = 'a'
let i: int = c // Compiler error, expected to have type 'int' but here has type 'char'

let x: int = 100000
let y: int = int16 x // Compiler error, expected to have type 'int' but here has type 'int16'
let z = int16 x // this compiles and runs, but the result still is -31072

Typing:

Nominal vs Structural

applies to static typing

Nominal

Very popular in mainstream languages like C#, C++, or Java

Types are identified by their respective names

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
// C#
class Employee { public string Name; }
class Animal { public string Name; }

string Hire(Employee employee)
{
  // some fancy logic here...
  return employee.Name;
}

var name = Hire(new Employee()); // This works
var name = Hire(new Animal()); // This doesn't as 'Hire' explicitly expects an 'Employee'

Structural

Also called row polymorphism

Types are identified by their respective structures and properties

Present under different forms in Elm, Go, TypeScript, Scala, OCaml, Haskell...

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
// Elm
hire: { name: String } -> String
hire entity =
  // Some fancy logic here...
  entity.name

hire { name = "Tomek Nowak" } // This works, returns the string 'Tomek Nowak'
hire { name = "Garfield" } // This also works, returns the string 'Garfield'

But there's more...

Structural subtyping

Beware the awesomeness!

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
// Elm
type alias TypeWithName a = { a | name: String }

hire: TypeWithName a -> String
hire entity =
  // Some fancy logic here...
  entity.name

hire { name = "Tomek Nowak", age = 25, gender = "male" } // This still works!
hire { name = "Garfield", canFly = False, hasPaws = True } // And this works too!

The actual structure of the types is checked as compile-time, making this super safe while giving a dynamic feel to the language

Structural typing

for implicit interface implementation

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
// Go
type Stringer interface {
  String() string
}

import ("fmt")

type User struct {
  name string
}

func (user User) String() string {
  return fmt.Sprintf("User: name = %s", user.name)
}

func main() {
  user := User{name: "Tomek Nowak"}
  fmt.Println(user) // fmt.Println(...) takes a Stringer interface as parameter
  //prints 'User: name = Tomek Nowak'
}

Duck Typing

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

basically the same as structural typing, but at runtime!

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
// Python
class Duck:
    def quack(self):
        print("Quack!")

class Dog:
    def bark(self):
        print("Woooof!")

def lets_quack(animal):
  animal.quack()

donaldTusk = Duck()
rex = Dog()

lets_quack(donaldTusk) // prints 'Quack!'
lets_quack(rex) // runtime error!

A word about Type Inference

https://twitter.com/java/status/967463386609954816

Basic Type Inference

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
// C++
void withoutTypeInference(std::vector<std::complex<double>> & myVector)
{
  std::vector<std::complex<double>>::const_iterator it = myVector.begin();
  // ...
}

void withTypeInference(std::vector<std::complex<double>> & myVector)
{
  auto it = myVector.begin();
  // ...
}

Advanced Type Inference

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
// F#
type Customer = {
  Id: Guid
  Name: string
  Age: int
}

// This is equivalent to:
// public Customer createCustomer(string name, int age) {...}
let createCustomer name age =
  {
    Id = Guid.NewGuid()
    Name = name
    Age = age
  }

// This will generate a compiler error
let newCustomer = createCustomer 18 "Tomek Nowak"

https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system

Part II


So, do you even type??

Let's start with a simple example

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
public interface ICustomerService
{
  Customer CreateCustomer(CustomerDto dto);
}

public class CustomerService : ICustomerService
{
  public Customer CreateCustomer(CustomerDto dto)
  {
    // implementation here
  }
}

A possible and plausible implementation

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
public Customer CreateCustomer(CustomerDto dto)
{
  var isValid = Validate(dto);
  if (isValid)
  {
    var newCustomer = _dbService.SaveCustomer(dto);
    return newCustomer;
  }
  else
  {
    return null;
  }
}

private bool Validate(CustomerDto dto)
{
  return dto.Code != "" && dto.Contact.Email.IsValid() && dto.Age >= 18;
}

Possible outcomes

value of dto

Result of Validate(...)

Result of CreateCustomer(...)

not null

true

new Customer

not null

false

null

null

throws exception

failure

Other possible scenarios:

  • _dbService is null -> throws exception
  • _dbService throws exception -> failure

Let's take a step back

1: 
2: 
3: 
4: 
public interface ICustomerService
{
  Customer CreateCustomer(CustomerDto dto);
}

Just another LOB app

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
public void DisplaySalesReport(DateRangeFilter dateRange)
{
  var canGenerateReport = await authService.GetPermissions(_session.GetCurrentUser());
  if (!canGenerateReport)
    return;
  
  var salesResults = await _salesService.GetSalesResults(dateRange, CurrentCountry);
  var products = await _productsService.RetrieveProductCatalog(dateRange.Year, ProductCategory);
  var salesReport = await ReportHelper.GenerateReport(salesResults, products, SelectedCustomers);
  Show(salesReport);
}

A few bug reports later...

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
public void DisplaySalesReport(DateRangeFilter dateRange)
{
  var canGenerateReport = await authService.GetPermissions(_session.GetCurrentUser());
  if (!canGenerateReport)
    return;
  
  if (dateRange != null && dateRange.Year != null)
  {
    var salesResults = await _salesService.GetSalesResults(dateRange, CurrentCountry);
    var products = await _productsService.RetrieveProductCatalog(dateRange.Year, ProductCategory);
    if (salesResults == null || products == null)
      return;

    if (SelectedCustomers == null)
      SelectedCustomers = new List<Customer>();
    var salesReport = await ReportHelper.GenerateReport(salesResults, products, SelectedCustomers);

    if (salesReport != null)
      Show(salesReport);
  }
}

Slapping a try-catch there too, just in case of!

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
public void DisplaySalesReport(DateRangeFilter dateRange)
{
  try
  {
    var canGenerateReport = await authService.GetPermissions(_session.GetCurrentUser());
    if (!canGenerateReport) return;
    
    if (dateRange != null && dateRange.Year != null)
    {
      var salesResults = await _salesService.GetSalesResults(dateRange, CurrentCountry);
      var products = await _productsService.RetrieveProductCatalog(dateRange.Year, ProductCategory);
      if (salesResults == null || products == null) return;

      if (SelectedCustomers == null) SelectedCustomers = new List<Customer>();
      var salesReport = await ReportHelper.GenerateReport(salesResults, products, SelectedCustomers);

      if (salesReport != null) Show(salesReport);
    }
  } catch (Exception)
  {
    Log.Error("oopsie... I guess we didn't handle all nulls and exceptions after all...")
  }
}

So now we know what the problem is

exceptions and nulls make our code brittle and unreliable

What can we do about it?

Get rid of nulls and exceptions altogether!

Let's see how to achieve this in 3 little steps

Step 1

Introducing sum types and pattern matching

Sum types can be seen as enums on steroids!

Also called Choice Types, Discriminated Unions, Tagged Unions...
Available in F#, Elm, Haskell, TypeScript, Scala, Rust, Swift...

Very powerful when combined with exhaustive pattern matching

Demo - sum types and pattern matching

sum types and pattern matching in a nutshell

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
// F#
type EntityId =
  | Customer of customerCode: string
  | SalesReceipt of documentId: Guid
  | Product of productId: integer

match entity with
| Customer code -> // the entity is a customer
| SalesReceipt id -> // the entity is a sales receipt
| Product id -> // the entity is a product
// The compiler makes sure you haven't forgotten a case!

Step 2

Getting rid of nulls using Option

Just a specific sum type with 2 cases!

Option makes the possible absence of value explicit

You have to deal with it at compile time

1: 
2: 
3: 
4: 
// F#
type Option<'a> =
  | Some of 'a
  | None
1: 
2: 
// Haskell and Elm
data Maybe a = Just a | Nothing
1: 
2: 
3: 
4: 
5: 
// Rust
enum Option<T> {
  None,
  Some(T),
}

Demo - fix nulls using Option

Option in a nutshell

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
// F#
type Contact = {
  Email: string
  PhoneNumber: string option // Explicilty mark the phone number as not mandatory
}

match contact.PhoneNumber with
| Some phoneNumber -> // deal with the phone number
| None -> // deal with the absence of phone number

Step 3

Getting rid of exceptions using Result

Yet another sum type!

Result makes both the success and error path explicit

1: 
2: 
3: 
4: 
// F#
type Result<'T, 'Error> =
  | Ok of 'T
  | Error of 'TError
1: 
2: 
// OCaml
type ('a, 'b) result = Ok of 'a | Error of 'b type
1: 
2: 
3: 
4: 
5: 
// Rust
enum Result<T, E> {
  Ok(T),
  Err(E),
}

Demo - fix exceptions using Result

Result in a nutshell

1: 
2: 
3: 
4: 
5: 
6: 
7: 
// F#
// Result<Customer, string> validateCustomer(Customer customer) {...}
let validateCustomer customer =
  if customer.Code <> "" && customer.Age >= 18 then
       Ok customer
  else
      Error "the customer is not valid!"

Enables Railway-Oriented Programming https://fsharpforfunandprofit.com/rop/

That's it!

What did we achieve?

Direct outcomes

  • no more null checks everywhere
  • no more exceptions and try-catches everywhere

Indirect outcomes

  • no more lies!
  • our models are more expressive (Option)
  • our code is more readable and explicit (Result)
  • the compiler is our friend -> "if it compiles, then it works"
  • TDD!

*Type Driven Development

Where to go from here?

Learn new languages (Elm, Elixir, F#, Rust, TypeScript)

Explore new concepts (FP, Actor Model...)

Thank You!