val collectSnips : par:'a -> seq<'b>

Full name: index.collectSnips
val par : 'a
Multiple items
val seq : sequence:seq<'T> -> seq<'T>

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

--------------------
type seq<'T> = System.Collections.Generic.IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
val codes : seq<obj>

Full name: index.codes
module Seq

from Microsoft.FSharp.Collections
val collect : mapping:('T -> #seq<'U>) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.collect
val replaceSnips : lookup:'a -> par:'b -> 'c

Full name: index.replaceSnips
val lookup : 'a
val par : 'b
Multiple items
type LiteralAttribute =
  inherit Attribute
  new : unit -> LiteralAttribute

Full name: Microsoft.FSharp.Core.LiteralAttribute

--------------------
new : unit -> LiteralAttribute
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 map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val newPars : (obj -> obj) list

Full name: index.newPars
type IDocProcessor =
  interface
    abstract member OnFormatDoc : 'a0 -> 'a1
    abstract member OnFormatSnippet : 'a0 -> 'a1
    abstract member OnParseDoc : 'a0 -> 'a1
    abstract member OnParseSnippet : 'a0 -> 'a1
  end

Full name: index.IDocProcessor
abstract member IDocProcessor.OnParseDoc : 'a0 -> 'a1

Full name: index.IDocProcessor.OnParseDoc
abstract member IDocProcessor.OnFormatDoc : 'a0 -> 'a1

Full name: index.IDocProcessor.OnFormatDoc
abstract member IDocProcessor.OnParseSnippet : 'a0 -> 'a1

Full name: index.IDocProcessor.OnParseSnippet
abstract member IDocProcessor.OnFormatSnippet : 'a0 -> 'a1

Full name: index.IDocProcessor.OnFormatSnippet
Multiple items
type MultiLangHandler =
  inherit obj
  new : unit -> MultiLangHandler
  override OnFormatSnippet : snip:'a -> 'b
  override OnParseSnippet : snip:'a -> 'b

Full name: index.MultiLangHandler

--------------------
new : unit -> MultiLangHandler
union case Option.None: Option<'T>
override MultiLangHandler.OnParseSnippet : snip:'a -> 'b

Full name: index.MultiLangHandler.OnParseSnippet
override MultiLangHandler.OnFormatSnippet : snip:'a -> 'b

Full name: index.MultiLangHandler.OnFormatSnippet
val doc : obj

Full name: index.doc
val snips : obj

Full name: index.snips
type Game =
  interface
    abstract member Draw : DrawingContext -> unit
    abstract member Initialize : unit -> unit
    abstract member Update : unit -> unit
  end

Full name: index.Game
abstract member Game.Initialize : unit -> unit

Full name: index.Game.Initialize
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
abstract member Game.Draw : DrawingContext -> unit

Full name: index.Game.Draw
type DrawingContext = | DC

Full name: index.DrawingContext
abstract member Game.Update : unit -> unit

Full name: index.Game.Update
Multiple items
type MyGame =
  inherit Game
  new : unit -> MyGame
  override Draw : ctx:DrawingContext -> unit
  override Initialize : unit -> unit
  override Update : unit -> unit

Full name: index.MyGame

--------------------
new : unit -> MyGame
override MyGame.Initialize : unit -> unit

Full name: index.MyGame.Initialize
union case Option.Some: Value: 'T -> Option<'T>
override MyGame.Update : unit -> unit

Full name: index.MyGame.Update
override MyGame.Draw : ctx:DrawingContext -> unit

Full name: index.MyGame.Draw
module Option

from Microsoft.FSharp.Core
val iter : action:('T -> unit) -> option:'T option -> unit

Full name: Microsoft.FSharp.Core.Option.iter
type IEvent<'T> = IEvent<Handler<'T>,'T>

Full name: Microsoft.FSharp.Control.IEvent<_>
type bool = System.Boolean

Full name: Microsoft.FSharp.Core.bool
val game : Game

Full name: index.game
val mario : obj

Full name: index.mario
val loop : x:'a -> Async<unit>

Full name: index.loop
val x : 'a
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
abstract member Game.Update : unit -> unit
abstract member Game.Draw : DrawingContext -> unit
union case Choice.Choice1Of2: 'T1 -> Choice<'T1,'T2>
union case Choice.Choice2Of2: 'T2 -> Choice<'T1,'T2>

Functional library design
NCrafts.io, Paris, 21-22 May

Tomas Petricek, fsharpWorks
@tomaspetricek | tomasp.net | fsharpworks.com

Before I tell you how to do things...


To those who look at the rich material provided by history (...) it will become clear that there is only one principle that can be defended under all circumstances and in all stages of human development. It is the principle: anything goes.

Paul Feyerabend, Against Method

Case study: F# Formatting

I don't know the answer, but this
worked nicely for one library!


  1. Formatting F# snippets
  2. Parsing Markdown documents
  3. Literate programming tools
  4. Documentation for .NET libraries

From highlighting F# snippets

And highlighting F# snippets

To documenting .NET libraries

The story of F# Formatting

F# Snippets web site

Sept 2010

Markdown parser (TryJoinads)

Jan 2012

F# Data documentation

Jan 2013

Deedle + ProjectScaffold

Oct 2013

FsLab Journal

Apr 2014

FsReveal project

Jul 2014

The story of F# Formatting

  • Took a long time and changed
    Many good things start as dirty hacks!

  • Does things we did not plan
    Composed and evolved from experiments

  • Used in unexpected ways
    Even these slides! How cool is that?

Functional library design principles

Functional library design principles


  • Evolving design
  • Composability
  • Avoid callbacks
  • Layers of abstraction

Evolving design

DEMO


From script to a library

From script...

  • Paket dependencies
  • Expose internals!
  • Copy and paste!

...to library

  • Publish on NuGet
  • Documentation
  • Reusable API

Composability

DEMO


Markdown with code formatting

Markdown with code formatting

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
let rec collectSnips par = seq {
  match par with
  | CodeBlock(code, "fsharp", _) ->
      // Extract F# code block for formatting!
      yield code
  | Matching.ParagraphNested(_, pars) ->
      for p in pars do
        yield! collectSnips p
  | _ -> () }

let codes = doc.Paragraphs |> Seq.collect collectSnips

Markdown with code formatting

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
let rec replaceSnips lookup par =
  match par with
  | CodeBlock(code, "fsharp", _) ->
      // Replace F# snippet with formatted HTML
      Literal(lookup.[code])
  | Matching.ParagraphNested(pn, nested) ->
      let pars = List.map (replaceSnips lookup) nested
      Matching.ParagraphNested(pn, pars)
  | other -> other

let newPars = doc.Paragraphs |> List.map replaceSnips

Avoid callbacks

Libraries

Call methods or functions

Frameworks

Implement an interface

No formal definitions, but callbacks are indicators!

Avoid callbacks and frameworks

Frameworks do not compose nicely!

Avoid callbacks and frameworks

Composing libraries is easy!

SAMPLE


Framework vs. library style

In the F# Formatting context

We want to customize the document:

  • Parse Markdown document
  • Split multi-language snippets
  • Format F# & other snippets
  • Generate HTML with tabs
  • Produce final HTML document

Used in Alea GPU tutorial by QuantAlea.

Framework approach

Pass interface implementation to Literate.Process:

1: 
2: 
3: 
4: 
5: 
6: 
type IDocProcessor =
  abstract OnParseDoc : Document -> Document
  abstract OnFormatDoc : Document -> Document
  abstract OnParseSnippet : Node -> Node
  abstract OnFormatSnippet : Node -> Node
  // Lots of methods we can override!

Framework approach

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
type MultiLangHandler() =
  inherit DocProcessor()
  let mutable snippets = []
  let mutable transformed = None
  // Collect all parsed snippets for later
  override x.OnParseSnippet(snip) =
    snippets <- snip::snippets
  // Transform when we have all & format
  override x.OnFormatSnippet(snip) =
    if transformed = None then
      transformed <- transformAll snippets
    lookup transformed snip
  • Frameworks lead to uninitalized states
  • Frameworks lead to mutation
  • Frameworks lead to inheritance complexity

Library approach

Provide more fine-grained functions:

1: 
2: 
3: 
4: 
5: 
6: 
let doc = Literate.ParseScriptString(input) // Library
let snips = collectCodeSnippets doc         // us
let snips = transformSnippets snips         // us
let doc = Literate.FormatLiterateNodes(doc) // Library
let doc = replaceSnippets snips doc         // us
Literate.WriteHtml(doc)                     // Library
  • We control the control flow
  • We control our additional state
  • Still call library-provided steps!

WALKTHROUGH


Inverting the flow in games

Class in a game framework

1: 
2: 
3: 
4: 
type Game =
  abstract Initialize : unit -> unit
  abstract Draw : DrawingContext -> unit
  abstract Update : unit -> unit

User just implements an abstract class!

  • Web frameworks love this!
  • But what is the contract?

Using a game framework

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
type MyGame() =
  inherit Game()
  let mutable x = 0
  let mutable mario = None

  override this.Initialize() =
    mario <- Some(Image.Load("mario.png"))
  override this.Update() =
    x <- x + 1
  override this.Draw(ctx) =
    mario |> Option.iter (fun mario ->
      ctx.Draw(x, 0, mario))

Game.Start(new MyGame())
  • Frameworks often need mutation
  • Framewokrs often need uninitalized state

Designing a game library

How to put the user in control? Use events!


1: 
2: 
3: 
4: 
type Game =
  member Update : IEvent<unit>
  member Draw : IEvent<DrawingContext>
  member IsRunning : bool

Designing a game library

1: 
2: 
3: 
// Initialize game and resources
let game = new Game()
let mario = Image.Load("mario.png")
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
// Recursive loop that runs until the end of the game
let rec loop x = async {
  if game.IsRunning then
    let! evt = Async.AwaitObservable
      (game.Update, game.Draw)
    match evt with
    | Choice1Of2() -> return! loop (x + 1)
    | Choice2Of2(ctx) ->
        ctx.Draw(x, 0, mario)
        return! loop x }
1: 
loop 0  // Start with x=0

Layers of abstraction

Layers of abstraction

Case study: F# lists

High-level

  • Higher-order funcs
  • map, filter etc
  • 85% of cases

Low-level

  • Recursive funcs
  • x::xs and []
  • Remaining 15%

Case study: Functional 3D

DEMO


FsReveal and F# Formatting

Summary

Libraries and frameworks

Libraries

  • Use multiple libs
  • Clear control flow
  • Layers of abstraction

Frameworks

  • One framework only
  • Many entry points
  • One way of doing things

Functional library design principles

Evolving design
Encourage early experiments

Composability
Allow unexpected uses

Avoid callbacks
Let the caller control things

Layers of abstraction
Make 85% tasks easy, 99% possible

Thank you!



Tomas Petricek, fsharpWorks

See functional-programming.net for books & trainings!
@tomaspetricek | tomasp.net | fsharpworks.com