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>
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!
- Formatting F# snippets
- Parsing Markdown documents
- Literate programming tools
- 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
DEMO
From script to a library
From script...
- Paket dependencies
- Expose internals!
- Copy and paste!
|
...to library
- Publish on NuGet
- Documentation
- Reusable API
|
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
|
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
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
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
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