type Item =
  {Title: string;
   DueDate: DateTime;}

Full name: index.Item
Item.Title: string
Multiple items
val string : value:'T -> string

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

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
Item.DueDate: DateTime
Multiple items
type DateTime =
  struct
    new : ticks:int64 -> DateTime + 10 overloads
    member Add : value:TimeSpan -> DateTime
    member AddDays : value:float -> DateTime
    member AddHours : value:float -> DateTime
    member AddMilliseconds : value:float -> DateTime
    member AddMinutes : value:float -> DateTime
    member AddMonths : months:int -> DateTime
    member AddSeconds : value:float -> DateTime
    member AddTicks : value:int64 -> DateTime
    member AddYears : value:int -> DateTime
    ...
  end

Full name: System.DateTime

--------------------
DateTime()
   (+0 other overloads)
DateTime(ticks: int64) : unit
   (+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : unit
   (+0 other overloads)
type Model = Item list

Full name: index.Model
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
type Message =
  | Add of Item
  | Reset

Full name: index.Message
union case Message.Add: Item -> Message
union case Message.Reset: Message
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
Multiple items
module Event

from Microsoft.FSharp.Control

--------------------
type Event<'T> =
  new : unit -> Event<'T>
  member Trigger : arg:'T -> unit
  member Publish : IEvent<'T>

Full name: Microsoft.FSharp.Control.Event<_>

--------------------
type Event<'Delegate,'Args (requires delegate and 'Delegate :> Delegate)> =
  new : unit -> Event<'Delegate,'Args>
  member Trigger : sender:obj * args:'Args -> unit
  member Publish : IEvent<'Delegate,'Args>

Full name: Microsoft.FSharp.Control.Event<_,_>

--------------------
new : unit -> Event<'T>

--------------------
new : unit -> Event<'Delegate,'Args>
union case Option.None: Option<'T>
val id : x:'T -> 'T

Full name: Microsoft.FSharp.Core.Operators.id
union case Option.Some: Value: 'T -> Option<'T>

TypeScript
for F# zealots







Tomas Petricek

University of Kent and fsharpWorks
tomas@tomasp.net | @tomaspetricek

TypeScript
for F# zealots







Tomas Petricek

University of Kent and fsharpWorks
tomas@tomasp.net | @tomaspetricek

Wrattler

My experience with TypeScript

Wrattler

New notebook system for data science

Interactive, smart dependency tracking and caching

Client-side in TyepScript!

What Wrattler does

Next generation of data science notebooks

Polyglot - mix multuple languages

Reproducible - dependency tracking

Interactive - do more in the browser

Smart - support for AI assistants

DEMO

Wrattler in Action

Building Wrattler

Why we choose TypeScript

  • I'm an F# person!
  • The team knew TypeScript
  • Jupyter uses TypeScript

Reflections on TypeScript

  • The Good: Decent for functional programming!
  • The Bad: Difficult trade-offs in type system
  • The Ugly: You'd think it has simple syntax

The Good

Functional programming with TypeScript

Elm architecture

Wrattler code structure

  • Immutable Model and Event
  • Pure update and render
  • Almost no dependencies

Why not just use React

  • Not very fancy user interface
  • Avoid fast-paced JavaScript world
  • Control over how things work
  • Core React is pretty simple!

DEMO

Elm architecture in F#

Domain modelling

Modelling TODO list with F# types

1: 
2: 
3: 
4: 
5: 
type Item = { Title : string; DueDate : DateTime }
type Model = list<Item>
type Message =
  | Add of Item
  | Reset

Elm-architecture operations

1: 
2: 
val update : Model -> Message -> Model
val render : (Message -> unit) -> Model -> Html

Functional TypeScript

Strict mode is great!

  • strictNullChecks to avoid null
  • noImplicitAny to avoid any
  • strict for more good things

Functional data types

  • Interfaces for simple record types
  • Unions with singleton types for DUs
  • Discipline to avoid mutation :-)

DEMO

Elm architecture in TypeScript

Elmish in TypeScript

Model and discriminated union for events

1: 
2: 
3: 
4: 
5: 
6: 
interface Model {
  count : number | null
}
interface ResetEvent { kind:"reset" }
interface UpdateEvent { kind:"update", by:number }
type Event = ResetEvent | UpdateEvent

Elm-architecture operations

1: 
2: 
render : (trigger:((event:TEvent) => void), state:TState) => VNode
update : (state:TState, event:TEvent) => TState

The Bad

Trade-offs in the type system

Extensibility

External language
Kernel with eval

Language plugin
Block with custom user interface

AI assistants
Clever component

Language plugins

Custom user interface, evaluation, dependencies

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
interface LanguagePlugin {
  language : string
  editor : Editor<EditorState, any>

  parse(code:string) : Block
  evaluate(context:EvaluationContext, node:Graph.Node)
    : Promise<EvaluationResult>
  bind(context:BindingContext, block: Block)
    : Promise<BindingResult>
  save(block:Block):string;
}

DEMO

Implementing language plugins

Classes vs. Interfaces (1/5)

When you might want a class

  • If you want object-oriented style
  • If you want checks using instanceof

When you do not want a class

  • If you want somewhat lighter syntax
  • If you want somewhat more flexibility

Classes vs. Interfaces (2/5)

Anonymous interface implementations are great!

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
const myLangPlugin : Langs.LanguagePlugin = {
  language: "merger",
  iconClassName: "fa fa-object-group",
  editor: mergerEditor,
  getDefaultCode: (id:number) => "",
  parse: (code:string) : MergerBlock => {
    let [outName, inputs] = code.split('=')
    return { language: "merger",
      output: outName, inputs: inputs.split(',') }
  }
}

Classes vs. Interfaces (3/5)

But switching to a class is pain

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
class MyLangPlugin implements Langs.LanguagePlugin {
  language = "merger"
  iconClassName = "fa fa-object-group"
  editor = mergerEditor
  getDefaultCode = (id:number) => ""
  parse(code:string) : MergerBlock {
    let [outName, inputs] = code.split('=')
    return { language: "merger",
      output: outName, inputs: inputs.split(',') }
  }
}

Classes vs. Interfaces (4/5)

Writing constructors is so much work!

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
export class JsLanguagePlugin implements LanguagePlugin  {
  readonly language: string
  readonly editor: Langs.Editor<JsState, JsEvent>
  readonly datastoreURI:string

  constructor(datastoreURI:string) {
    this.language = "javascript"
    this.editor= javascriptEditor
    this.datastoreURI = datastoreURI
  }
  parse (code:string): JavascriptBlockKind{
    return new JavascriptBlockKind(code);
  }
}

Classes vs. Interfaces (5/5)

Can I get F# implicit class syntax please?

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
type JsLanguagePlugin(datastoreURI:string)
  let language = "javascript"
  let editor= javascriptEditor
  let datastoreURI = datastoreURI

  interface LanguagePlugin with
    member x.parse(code:string) =
      JavascriptBlockKind(code)

Breaking the type system (1/3)

You cannot fully avoid the any type

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let response = await axios.get(url, {headers:{Inputs:header}});
let data = response.data;

return data.map(r => ({
    name: r.name,
    path: r.path.split("/").filter((p:string) => p != "")
  }));

F# type providers are nice, but don't always help

You can get more checks with noImplicitAny

Breaking the type system (2/3)

It is still JavaScript underneath...

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
interface Variable {
  name : string
}
type Scope = { [variable:string]: Variable }

function getVarLength(scope:Scope, k:string) : number {
  return scope[k] != undefined ? scope[k].name.length : -1;
}

Can getVarLength fail because name is undefined?

Breaking the type system (3/3)

It is still JavaScript underneath...

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
interface Variable {
  name : string
}
type Scope = { [variable:string]: Variable }

function getVarLength(scope:Scope, k:string) : number {
  return scope[k] != undefined ? scope[k].name.length : -1;
}

let evil = getVarLength({}, "toString") // Boom!

Poor type inference (1/3)

F# can typically infer types from the first use

1: 
2: 
3: 
4: 
5: 
let mutable outputs = []
let addOutput = fun f ->
  outputs <- f::outputs

evaluateCode code addOutput

Poor type inference (2/3)

Type inference in the strict mode is poor

1: 
2: 
3: 
4: 
5: 
var outputs = [];
var addOutput = function(f) {
  outputs.push(f)
}
evaluateCode(code, addOutput)

Poor type inference (3/3)

Type inference in the strict mode is poor

1: 
2: 
3: 
4: 
5: 
var outputs : ((id:string) => void)[] = [];
var addOutput = function(f:(id:string) => void) {
  outputs.push(f)
}
evaluateCode(code, addOutput)

The Ugly

Syntax for functional programming

AI assistants

Tools for semi-automated data wrangling

New code block with custom user interface

DEMO

Wrattler AI assistants

DEMO

Implementing AI assistant code block

Two function syntaxes (1/2)

Function definition using a lambda

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let createAiaEditor = (assistants:AiAssistant[])
    : Langs.Editor<AiaState, AiaEvent> => ({
  initialize: (id:number, block:Langs.Block) => {  
    let aiaBlock = <AiaBlock>block
    return { id: id, block: aiaBlock, chain: [],
      expanded: false, assistants: assistants }
  }
});

Two function syntaxes (2/2)

Explicit function using a keyword

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
function createAiaEditor(assistants:AiAssistant[])
    : Langs.Editor<AiaState, AiaEvent> {
  return { initialize: (id:number, block:Langs.Block) => {  
    let aiaBlock = <AiaBlock>block
    return { id: id, block: aiaBlock, chain: [],
      expanded: false, assistants: assistants }
  } };
};

Generating HTML (1/2)

Building HTML nodes is hard work!

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let previewButton =
  h('button', { onclick:() =>
    ctx.evaluate(cell.editor.id) }, ["Evaluate!"] )    
let spinner =
  h('i', {class: 'fa fa-spinner fa-spin' }, [])
let preview = h('div', {class:'preview'}, [
  (cell.code.value == undefined) ?
    (cell.evaluationState == 'pending') ? spinner : previewButton :
  (createOutputPreview(cell, cell.code.value))]);

Generating HTML (2/2)

F# comprehensions are so nice!

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
let preview = h?div ["class"=>"preview"] [
  match cell.code.value, cell.evaluationState with
  | None, "pending" ->
      yield h?i ["class" => "fa fa-spinner" ] []
  | None, _ ->
      yield h?button [ "onclick" =!> fun _ _ ->
        ctx.evaluate cell.editor.id ] [ text "Evaluate!" ]
  | Some value, _ ->
      yield createOutputPreview cell value
]

Conclusions

TypeScript for F# Zealots

TypeScript for F# Zealots

Lessons learned from Wrattler project

Can support functional patterns

Works fine with Elm architecture

Not designed for functional style

Trying to be JavaScript or not?

F# for the web

Functional core

Safe and clean

Extra integration

Makes trade-offs

TypeScript

Many styles

OOP, functional

Does not hide JS

Cloudy mix of all

TypeScript for F# Zealots

TypeScript experience
Elmish architecture works!
Hard to be disciplined

Project context
Team, community, app type
Would I choose TypeScript again?


Tomas Petricek, University of Kent and fsharpWorks
tomas@tomasp.net | @tomaspetricek