Jason Rametta Blog

Software Developer from Montreal

F# for JS Devs

I recently gave an internal talk at my work about the similarities of F# compared to JS. It was generally well received, and I would like to convert that talk into a blog post for others who may be interested.

This is by no means an exhaustive list of features in F#, but the point of this post is to show familiar JS code and how it can be written equivalently in F#, because I believe showing examples like this is the best way of learning a new language and has a better chance of adoption.

Intro

F# is a functional language that runs on the .NET runtime. It is cross platform now with the intro of .NET Core so it can be written and ran on any machine. It is immutable by default, but is completely interoperable with C# or VB. It is inspired by Haskell, Scala, Erlang, C# and Python.

F# can be used for creating servers, scripts, desktop apps and mobile apps. (Even webapps, by compiling to JS directly with tools like fable)

Functions

Functions are the core of F#. There are essentially two types of functions, named and anonymous. The syntax is similar to JS but slightly shorter. In F#, all functions are curried automatically, which means all functions can be partially applied without any extra work.

JS

const add = (x, y) => x + y
const mul = x => y => x * y // curried
add(4, 4) // 8
mul(4)(4) // 16

F#

let add x y = x + y
let mul x y = x * y
add 4 4 // 8
mul 4 4 // 16

// anonymous
let sub = fun x y -> x - y
sub 8 4 // 4

Composition

Function composition is the process of passing the output of one function as the input to another function. In JS, one would need to nest their functions, or use a pipe or compose function as a helper to achieve this. In F# there is the pipeline operator |>, the forward composition operator >> and backwards composition operator <<.

Pipeline Operator

The pipeline operator just allows having the function argument be infront of the function instead of after it.

JS

const add3 = x => x + 3
const mul5 = x => x * 5
const div2 = x => x / 2
div2(mul5(add3(97))) // 250

F#

let add3 x = x + 3
let mul5 x = x * 5
let div2 x = x / 2
97 |> add3 |> mul5 |> div2 // 250

Composition Operator

The composition operator allows combining functions into one. The difference between this and the pipeline is that only functions can be composed together, whereas the pipeline can take any value and pass it to the next function.

JS

const compose = require('..')
const add3 = x => x + 3
const mul5 = x => x * 5
const div2 = x => x / 2
const doMath = compose(div2, mul5, add3)
doMath(97) // 250

F#

let add3 x = x + 3
let mul5 x = x * 5
let div2 x = x / 2
let doMath = add3 >> mul5 >> div2
// or backwards
let doMath = div2 << mul5 << add3
doMath 97 // 250

Lists

F# lists are pretty similar to JS arrays. Although F# has 3 types of array-like collections. Lists, Arrays, and Sequences. But I'll just focus on Lists because they are the richest.

List Mapping

List mapping looks almost the same in F# as it does in JS, except for the fact that you must use the List.map function instead of using the array prototype to dot chain like you do in JS.

JS

const data = [1, 2, 3]
data.map(x => x * 2)
// [2, 4, 6]

F#

let data = [1; 2; 3]
List.map (fun x -> x * 2) data
// [2, 4, 6]

List Transformations

JS is praised for it's rich array prototype functions like map, filter, find, reduce. F# has all of those, and more than 60 others! Such as List.sum, List.average, List.distict, List.isEmpty, List.chunkBySize and many many more.

JS

[1, 2, 3]
    .map(x => x * 2)
    .filter(x => x > 3)
    .reduce((acc, x) => acc + x, 0)

F#

[1; 2; 3]
    |> List.map (fun x -> x * 2)
    |> List.filter (fun x -> x > 3)
    |> List.sum

Conditionals

JS has the classic if-else syntax and also the ternary operators. F# does NOT have a ternary operator, but it does have if-else. Ternary is not really needed in F# because everything is implicitly returned anyways. The great thing about F# is that you will rarely need the if-else syntax because of pattern matching (explained below). Regardless, here is an example.

JS

const bigify = x => x > 4 ? 'big' : 'small'
bigify(2) // 'small'
bigify(5) // 'big'

F#

let bigify x = if x > 4 then "big" else "small"
bigify 2 // "small"
bigify 5 // "big"

Objects / Records

The equivalent of JS objects would be the F# records. Notable differences are that records always need to be associated to a type, they are reference types by default, and they are immutable. So you can not update an existing record, you would need to create a new one and copy the values.

JS

const data = {
  name: 'jason',
  cool: true,
  age: 3.14
}

// immutably update an object by creating a new object
const data2 = {
  ...data,
  age: 2.16
}

F#   *Needs a type

let data =
  { name = "jason"
    cool = true
    age = 3.14 }

// immutably update a record by creating a new record
let data2 =
  { data with age = 2.16 }

Record Types

The above examples are not exactly possible in F# without specifying a type first.

A record type defines the structure of a record. You do not need to assign the type to the variable holding the data because of F#‘s strong type inference. The compiler will infer data types based on the properties defined. So in the example below, the compiler knows that data is a Person type because it has all the exact same fields defined.

F#

type Person =
  { name: string
    cool: bool
    age: float }

let data =
  { name = "jason"
    cool = true
    age = 3.14 }

Enum Types

There are no direct comparisons in JS for enums, unless you use an objects with ints, but it is not exactly the same.

F#

// enum
type CoolLevel = 
  | Good
  | Medium
  | Bad

type Person =
  { name: string
    age: float
    cool: CoolLevel } // requires a value from the enum

let data =
  { name = "lyagushka"
    age = 3.14
    cool = Good } // assign Good because it is in the enum

Discriminated Unions Types

To get the equivalent of Union Types in JS, you would have to use some 3rd party module to get a consistent declaration of types, such as DaggyJS.

Although Daggy is great in JS, it's pattern matching capabilities are only as good as JS can allow. This is where F# starts to shine.

If you need an explanation of union types, see this article, it will explain it much better than I can.

Below is an example of an equivalent JS daggy type vs a native F# union type, and a peak of pattern matching at the bottom.

JS

const { taggedSum } = require('daggy')

const ProductPage = taggedSum('ProductPage', {
  Loading: [],
  Error: ['msg'],
  Success: ['product']
})

const product = {
  name: 'Red Shoe',
  price: 3.14
}

const state = ProductPage.Success(product)

// pattern match
state.cata({
  Loading: () => `<div>Loading...</div>`,
  Error: msg => `<div>${msg}</div>`,
  Success: p => `<div>${p.name}</div>`
})

F#

type Product =
  { name: string
    price: float }

type ProductPage = 
  | Loading
  | Error of string
  | Success of Product

let product =
  { name = "Red Shoe"
    price = 3.14 }

let state = Success product

// pattern match
match state with
| Loading -> "<div>Loading...</div>"
| Error msg -> "<div>" + msg + "</div>"
| Success p -> "<div>" + p.name + "</div>"

Pattern Matching

Pattern matching is popular in ML style languages because of how powerful they can be. Think of it as a switch-case statement on steroids. In F#, using the syntax of match [anything] with you can successfully figure out what the type OR value is of anything. Completely avoiding if-else or switch-case statements.

Booleans

Booleans are straight forward because they can only be 1 of 2 things, true or false.

let age = 6

match age > 12 with
| true -> printf "Teen"
| false -> printf "Not teen"

Numbers

Numbers are not as straight forward as booleans because there are potentially an infinite amount of matching possibilities, so when trying to match numbers, you will be forced to provide a default pattern by using an underscore incase no pattern is matched.

let age = 5

match age with
| 13 -> "teen"
| 1 -> "One Year Old"
| 4 | 5 -> "little" // 4 or 5 will match here
| x when x < 0 -> "not alive" // conditional logic
| _ -> "any other age" // default incase age is not matched with anything

Lists

Matching with lists is even cooler because you can use the underscore as a wildcard for any value inside the list.

let myList = [1; 2]

match myList with
| [] -> "empty list"
| [ _ ] -> "list has 1 item"
| [ _; 5 ] -> "list has 2 items, 2nd item is 5"
| [ _; _; _ ] -> "list has 3 items"
| _ -> "list does not match any of the above patterns"

Monads

Monads are a big topic, I even wrote an entire article about monads in JS.

In F#, some monads are built in, such as the Option type, and no further work is needed to use besides typing Some or None.

JS

const { taggedSum } = require('daggy')

const Maybe = taggedSum('Maybe', {
  Just: ['value'],
  Nothing: []
})

const { Just, Nothing } = Maybe

const data = Just(50)

data.cata({
  Just: x => console.log(`Value: ${x}`), // 50
  Nothing: () => console.warn("Nothing here")
})

F#

let data = Some(50)

match data with
| Some x -> printf "Value: %i" x
| None -> printf "Nothing here"

Typing

A brief note about typing functions in F#. Below I wrote the exact same function 4 times, each with a different way of defining the types.

The first one has implicit types, letting the compiler infer the types based on the callers and the data passed to it.

The second defines types for each parameter and then defines the return type.

The third and fourth use a type signature and an anonymous function to define the types.

All of these are valid and each can be used for different use cases.

F#

// inferred types
let add x y = x + y

// explicit types
let add (x: float) (y: float): float = x + y

// explicit inline type signature
let add: float -> float -> float = fun x y -> x + y

// explicit separate type signature
type Add = float -> float -> float
let add: Add = fun x y -> x + y

HTTP Requests

A great part of JS is the easy to work with Promise type for doing async actions, such as HTTP requests.

Async is built into F# aswell, by using the async keyword. Here is an example of an equivalent http request of getting the html of a page.

JS

const axios = require('axios')

axios
  .get('https://github.com/rametta')
  .then(({ data }) => console.log(`HTML: ${data}`))
  .catch(console.error)

F#

// sync
let html = Http.RequestString("https://github.com/rametta")

// async
async { let! html = Http.AsyncRequestString("https://github.com/rametta")
        printfn "%d" html.Length }
|> Async.Start

Other cool F# stuff

Briefs snippets of other neat F# features.

Range operator

Use two dots to define a range quickly.

let myList = [ 1..5 ]
// [1; 2; 3; 4; 5]

Mutable Keyword

Use the mutable keyword as an escape hatch when wanting to mutate variables.

let mutable data = 6
data <- 8

Yield Keyword

let mySeq = seq {
  for i in 1..10 do
  for j in 10..15 do
  yield i * j
}

Tuples

let myTuple = (5, "hello")

let typedTuple: int * string = (5, "hello")

I hope this article shed some light on how similar F# is to JS, and I hope it encourages you to use it in future projects.