F# for JS Devs

rametta profile image Jason Updated on ・8 min read

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.


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 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.


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


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


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.


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


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.


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


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


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.


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


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.distinct, List.isEmpty, List.chunkBySize and many many more.


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


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


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.


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


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.


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

// immutably update an object by creating a new object
const data2 = {
  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.


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.


// 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.


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
  Loading: () => `<div>Loading...</div>`,
  Error: msg => `<div>${msg}</div>`,
  Success: p => `<div>${p.name}</div>`


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 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 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


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 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.


const { taggedSum } = require('daggy')

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

const { Just, Nothing } = Maybe

const data = Just(50)

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


let data = Some(50)

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


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.


// 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.


const axios = require('axios')

  .then(({ data }) => console.log(`HTML: ${data}`))


// 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


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.

If you would like to learn more about F#, check out fsharpforfunandprofit!

Feel free to follow me on twitter! @rametta

Posted on Mar 16 '19 by:

rametta profile



Software Developer in Montreal, Canada.


markdown guide

Very nice write up, I was originally looking at F# when working for a client with .NET and it looked really wonderful especially the fabel compiler. However i settled on purescript because of things like HKTs though i wonder if you can simulate them in F# like in TS for example ?

check out fsharpforfunandprofit!

those posts on thinking functionally and functional design are excellent :)


Apparently there are ways to simulate HKTs in F#, but seems to be beyond my knowledge on why HKTs are needed anyways. Maybe one day I will understand them.


Awesome article, good thing you made it a blog! Small typo List.distiNct. Woah never looked into async with fsharp, looks weird when compared to C#. I love how currying is integrated and composition is through operators, can’t wait for TC39 to move pipe operator into ES :). Thanks for writing this.


Yes can't wait for the pipe operator to be in ES too, will be game changer!


Nice article, flawed by quite a few minor grammar errors and spelling/typing mistakes - as in "Patern" in the Pattern Matching subtitle. As well, you don't mention the availability of the backwards pipe "<|" in the pipe/composition. Also, your description of List's, Array's, and Sequence's as somewhat equivalent to JavaScript List's isn't really correct. First, pure Sequence's have no memorization/storage unless used with Seq.cache, in which case they have a backing store of a growable DotNet List array (not the same as a F# List) or are generated from other forms of backing store such as List or Array by using "toSeq"; they also are quite execution time inefficient due to many nested function calls as they are implemented with DotNet iterators, nor are they able to be directly indexed in O(1) time as they can only have a particular index "chased" through the link chain. Secondly, F# List's are linked (non-lazy) lists and thus aren't memory use efficient as the link means they consume up to 50% memory overhead (for the case of a List of numbers) (They also shouldn't be used as if they were lazy lists as in Haskell since that can cause stack overflows). Thirdly, Array's are the most efficient in terms of execution and memory use for many uses as they are directly indexed regions of (heap) memory and can be equivalent to JavaScript primitive number array's for newer > ES5 JavaScript. Finally, it should be noted that JavaScript List's have a couple of uses - loosely equivalent to array's when numerically indexed, but more equivalent to a F# Map (actually more like a mutable hashed DotNet Dictionary) when associatively indexed by a non-numeric or sparsely indexed.


Thanks for the feedback Gordon


Nice article. Even more than the other languages you listed, F# is based on OCaml. You might even say that it is OCaml for .NET + OO and C#-compatibility features.


Great article! I'm currently learning Elm and this was very interesting considering how similar it is to F#. Elm is a lot more minimal though. Thanks for the comparison to js too, of course :)


Elm is a great language for generating client side web pages and particularly good for learning programming (purely functionally) as it has greatly reduced complexity. However, due to the enforcement of this simplicity, it has some limitations that Fable (a F# transpiler to JavaScript) doesn't have and by using the Fable Elmish interface library for React, is almost as easy to use while providing much enhanced capabilities if one needs them.


Glad you liked it 🙂


Might be worth adding a reference to anonymous records in the Records/Objects section: devblogs.microsoft.com/dotnet/anno... .


Nice article. Thank you for sharing.


"In F#, all functions are curried automatically". That's not right. F# equivalent should be let add(x,y) = x + y


That looks like a function that takes one tuple as an argument and destructures it, and since JS does not really have tuples - it does not seem equivalent to me.
Especially since in F# you can do let add(x,y)(z,a) = x + y + z + a

But correct me if I'm wrong.