val updateParameters : t:int -> s:int -> tps:'a -> 'b
Full name: index.updateParameters
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<_>
val sprintf : format:Printf.StringFormat<'T> -> 'T
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val log : value:'T -> 'T (requires member Log)
Full name: Microsoft.FSharp.Core.Operators.log
val private pquery : obj
Full name: index.pquery
val private filterPlugins : q:'a -> config:'b -> 'c
Full name: index.filterPlugins
val q : 'a
val config : 'a
val pres : 'a
union case Option.Some: Value: 'T -> Option<'T>
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|_| )
union case Option.None: Option<'T>
val printmsg : (int -> string)
Full name: index.printmsg
val registerDependencies : unit -> unit
Full name: index.Application.registerDependencies
val path : 'a
val protocols : 'a
val ignore : value:'T -> unit
Full name: Microsoft.FSharp.Core.Operators.ignore
val translations : 'a
type bool = System.Boolean
Full name: Microsoft.FSharp.Core.bool
type Result<'a,'b> =
| Success of 'a
| Failure of 'b
Full name: index.Result<_,_>
union case Result.Success: 'a -> Result<'a,'b>
Multiple items
union case Result.Failure: 'b -> Result<'a,'b>
--------------------
active recognizer Failure: exn -> string option
Full name: Microsoft.FSharp.Core.Operators.( |Failure|_| )
val compose : f:('a -> 'b) -> g:('c -> 'a) -> x:'c -> 'b
Full name: index.compose
val f : ('a -> 'b)
val g : ('a -> 'b)
val x : 'a
val h : (int -> 'a)
Full name: index.h
val a : 'a
Full name: index.a
val comose : f:('a -> 'b) -> g:('c -> 'a) -> x:'c -> 'b
Full name: index.comose
union case Result.Failure: 'b -> Result<'a,'b>
module Application
from index
namespace System
namespace System.Xml
namespace System.Xml.Serialization
type 'T list = List<'T>
Full name: Microsoft.FSharp.Collections.list<_>
val using : resource:'T -> action:('T -> 'U) -> 'U (requires 'T :> System.IDisposable)
Full name: Microsoft.FSharp.Core.Operators.using
namespace System.Collections
namespace System.Collections.Generic
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 set : elements:seq<'T> -> Set<'T> (requires comparison)
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
type 'T option = Option<'T>
Full name: Microsoft.FSharp.Core.option<_>
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
val double : value:'T -> float (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.double
--------------------
type double = System.Double
Full name: Microsoft.FSharp.Core.double
Typical C# day
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
|
public interface ILaboratoriesServiceContract
{
Laboratory GetLaboratoryById(long id);
IList<Laboratory> FindLaboratories(LaboratoryFilter filter);
long AddLaboratory(Laboratory laboratory);
bool UpdateLaboratory(Laboratory laboratory);
bool DeleteLaboratory(long IdLaboratory);
}
|
actually, this interface has too many responsabilites (is not SOLID)
anyway, we can try to implement a smart solution
I said smart, because it was mine
Wrap results that can fail
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
|
public interface ILaboratoriesServiceContract
{
SvcResult<Laboratory> GetLaboratoryById(long id);
SvcResult<IList<Laboratory>> FindLaboratories(LaboratoryFilter filter);
SvcResult<long> AddLaboratory(Laboratory laboratory);
SvcResult<bool> UpdateLaboratory(Laboratory laboratory);
SvcResult<bool> DeleteLaboratory(long IdLaboratory);
}
|
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
|
public class SvcResult<T>
{
public T Value { get; private set; }
public SvcResult(T value)
{
this.Value = value;
}
public SvcResult(T value, string error)
: this(value)
{
if (!string.IsNullOrEmpty(error))
{
this.Errors = new List<string>();
this.Errors.Add(error);
}
}
// some code removed...
}
|
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
|
public ActionResult Edit(long laboratoryId)
{
var res = this.svc_laboratory.GetById(laboratoryId);
if (res.HasErrors)
{
string msg = string.Join(System.Environment.NewLine, res.Errors.ToArray());
return new HttpNotFoundResult(msg);
}
else return View("Form", new LayoutableModelBase()
{
Id = laboratoryId,
Model = res.Value,
});
}
|
but...
if you have to compose somehow the results of one service with another ones...
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
|
public SvcResult<bool> Assign(MassiveFamilies assignation)
{
MassiveFamiliesBusinessRules rules = new MassiveFamiliesBusinessRules(assignation, context);
if (!rules.AreValid())
{
return new SvcResult<bool>(false, rules.GetErrors());
}
else
{
foreach (int familyId in assignation.Ids)
{
//updating calibration by using selected status
var family = svc_families.GetById(familyId);
if (family.HasErrors || family.Value == null)
throw new ApplicationException("Impossible. There is no way to retrieve an unexisting family by id " + familyId);
family.Value.CustomerId = this.context.Current.CustomerId;
var res = svc_families.Create(family.Value);
if (res.HasErrors) //throws
throw new ApplicationException(string.Join(" ", res.Errors));
}
return new SvcResult<bool>(true);
}
}
|

This is heavy, Doc!
(Marty McFly, two days ago)
Enter F# magic

- Expressiveness
- Type Safety
- Conciseness
Expressiveness
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
|
let updateParameters (t:int) (s:int) (tps:Protected<Api.TranslatedProtocol>) =
checkHash "concurrency error, someone changed the same protocol while you were editing it"
>=> checkIntegrity "data integrity error, there are parameters that are not from this protocol"
>=> validateIdentifiers
>=> checkTranslationKeyNotDefault DEFAULT_TRANSLATION_KEY (sprintf "%s is not a valid TranslationKey" DEFAULT_TRANSLATION_KEY)
>=> checkTranslationKeysUniques "duplicated TranslationKey in this protocol"
>=> prepareChanges
>=> Utils.applyChanges "unable to apply changes"
>> Utils.log "prot updated" "prot not updated:" <| (t, s, tprot)
|
Expressiveness (2)
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
|
(* Query parser *)
let private pquery =
pAll
<|> pPlugin
<|> pLeftAndRight
<|> pKeyAndValue
<|> pKey
<|> pValue
<|> pIdentifier
<|> pName
<|> pVersion
<|> pNaive
let private filterPlugins q config =
let pres = match run pquery q with
| Success(result, a, b) -> Some result
| Failure(error, _, _) -> None
|
Type Safety
1:
2:
|
let printmsg = sprintf "%d bottles of beer, standing on a wall"
in printmsg 42
|
Conciseness
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
|
module Application =
let registerDependencies () =
(* DI ? We don't need DI where we are going *)
let path = ProtocolsEditor.Infrastructure.Config.protocols_file_path
let protocols = ProtocolsEditor.Application.ReadProtocols.getProtocols path
ProtocolsEditor.Application.Repositories.Protocols.initializeRepo protocols.Value |> ignore
let translations = ProtocolsEditor.Application.Persistence.Translations.loadFromDirectoryOf path
ProtocolsEditor.Application.Repositories.Translations.initializeRepo translations |> ignore
|
Data types
- Algebraic data types
- Product types
- Immutable types
The road to F#
Let's begin with some inocuous C# code
1:
2:
3:
4:
|
private bool Launch(Missiles m)
{
ThereIsNoPeopleArround() && m.Launch();
}
|
At least the method is private!
How && works?

from A and B the computer can calulate A&&B
(no rocket science, at this point)
but...
this is the same as saying that A&&B is
- B if A succeeds,
- and is false otherwise
so
1:
|
ThereIsNoPeopleArround() && m.Launch();
|
- if && had not short-circuited the evaluation,
- independently of the value of ThereIsNoPeopleArround()
the missiles will have been launched
1:
2:
3:
|
type Result<'a,'b> =
| Success of 'a
| Failure of 'b
|
Interlude: function composition
\(f: A \mapsto B\)
\(g: B \mapsto C\)
then
\(h: A \mapsto C\)
where
\(h(x) = (g \cdot f)(x) = g(f(x))\)
Now in code
1:
|
let compose f g x = f (g x)
|
then when we have 2 particular f & g
1:
2:
|
let h = compose f g
let a = h 3
|
1:
2:
3:
|
let comose f g x = f (g x);;
val comose : f:('a -> 'b) -> g:('c -> 'a) -> x:'c -> 'b
|
We don't like to write compose
1:
|
let (>>) g f x = f (g x)
|
or point free style
1:
|
let (>>) g f = compose f g
|
then when we have 2 particular f & g
1:
2:
|
let h = g >> f
let a = h 3
|
We don't like to lose the meaning of what we're doing
the idiomatic F# uses pipes instead
Precedence is important, look:
1:
2:
3:
4:
5:
6:
|
checkNotExists "this protocol already exists"
>=> createProtocol
>=> checkIntegrity "data integrity error, there are parameters with incorrect protocol information"
>=> prepareChanges
>=> Utils.applyChanges "unable to create protocol"
>> Utils.log "created protocol" "protocol not created:" <| par
|
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
|
module ROP =
type Result<'a,'b> =
| Success of 'a
| Failure of 'b
let (>=>) f g x =
match f x with
| Success s -> g s
| Failure f -> Failure f
|
Shared resource
- ProtocolInformation.xml (14M)
+
- ~60 .resource files
that needs to be updated safely
- no concurrency
- and no conflicts
- and all the files at once.
Solution
Single api to update the system that provides optimistic concurrency + error reporting in the failing case.
The ideal case would be to be able to
- Check if there is any concurrency issue
- Validate interity of the data
- Validate integrity of the translations
- Update the data
- Log everything that happened or failed and report errors
Implementation
From an implementation point, the solution materializes in a SPA application
that actually is a thin layer of a WebAPI.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
|
open ProtocolsEditor.Application.UseCases
open ProtocolsEditor.Infrastructure.Utils
[<RoutePrefix("api/protocols/{boardTypeId}/{boardSubTypeId}")>]
type ProtocolController() =
inherit ApiController()
/// Replaces the current protocol with the posted protocol
[<HttpPut>]
[<Route("")>]
member x.Put(t: int, s: int, ps: Protected<Api.TranslatedProtocol>) =
Protocols.updateParameters t s ps |> Utils.asResponse <| x.Request
|
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
|
let updateParameters (t:int) (s:int) (tps:Protected<Api.TranslatedProtocol>) =
checkHash "concurrency error, someone changed the same protocol while you were editing it"
>=> checkIntegrity "data integrity error, there are parameters that are not from this protocol"
>=> validateIdentifiers
>=> checkTranslationKeyNotDefault DEFAULT_TRANSLATION_KEY (sprintf "%s is not a valid TranslationKey" DEFAULT_TRANSLATION_KEY)
>=> checkTranslationKeysUniques "duplicated TranslationKey in this protocol"
>=> prepareChanges
>=> Utils.applyChanges "unable to apply changes"
>> Utils.log "prot updated" "prot not updated:" <| (t, s, tprot)
|
Domain Model
(images/class-diagram) == record types
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
|
namespace ProtocolsEditor.Domain.Model
module Protocols =
open System
open System.Xml.Serialization
type ProtocolInformation = {
LastUpdate: DateTime
Parameters: ProtocolParameterInformation list
Sensors: ProtocolParameterInformation list
}
|
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:
|
namespace Definitions.Common.Domain
{
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
[Serializable]
[XmlRoot(Namespace = "www.foobar.com/protocols")]
public sealed class ProtocolInformation
{
public ProtocolInformation()
{
this.Parameters = new List<ProtocolParameterInformation>();
this.Sensors = new List<ProtocolParameterInformation>();
}
[XmlElement]
public DateTime LastUpdate { get; set; }
[XmlArray("Parameters")]
[XmlArrayItem("Parameter")]
public List<ProtocolParameterInformation> Parameters { get; set; }
[XmlArray(ElementName = "Sensors")]
[XmlArrayItem(ElementName = "Sensor")]
public List<ProtocolParameterInformation> Sensors { get; set; }
}
}
|
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
|
type ProtocolParameterInformation = {
Behavior: int
BoardSubtypeId: int
BoardTypeId: int
DefaultGraphicRender: int
FilterDefinitionInformation: FilterDefinitionInformation option
IsComputed: bool
IsPVPortalComputed: bool
MainParameter: bool
Mode: int
ParameterOutputMode: int
ProtocolParameterIdentifier: int
PVPortalComputeOperation: int
Tipus: int
TranslationKey: string
TranslationResourceName: string
ViewModeType: int
}
|
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
|
type FilterDefinitionInformation = {
CoefficientEvaluation: int
ComputedReferenceClassName: string
DailyGrouping: int
DailyPeriod: int
DecimalPlaces: int
GraphicRender: int
InstantaneousGrouping: int
InstantaneousPeriod: int
InvalidValues: int list
IsSpecialFormat: bool
MainCoefficient: double
MeasureUnit: string
MonthlyGrouping: int
MonthlyPeriod: int
ParameterSubType: int
ParameterType: int
SecondCoefficient: double
SpecialFormatMode: int
TranslationTableInformation: TranslationTableInformation option
YearlyGrouping: int
}
|
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
|
type TranslationTableDetailInformation = {
Key: double
TranslationKey: string
}
type TranslationTableInformation = {
TranslationResourceName: string
TranslationTableInformations: TranslationTableDetailInformation list
TranslationTableTypeFullName: string
}
|
1:
2:
3:
4:
5:
|
type Board = {
BoardTypeId: int
BoardSubtypeId: int
ParameterIds: int list
}
|
Theory
- Inmutabilty by default (actually, no
mutable field used in this editor!)
- Functional
Practice
- You need to mutate things: Lenses
- You need some objects
I <3 F#
