DEV Community

Jason
Jason

Posted on • Updated on

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

F#

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

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

F#

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

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.

JS

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

F#

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

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'
Enter fullscreen mode Exit fullscreen mode

F#

let bigify x = if x > 4 then "big" else "small"
bigify 2 // "small"
bigify 5 // "big"
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>`
})
Enter fullscreen mode Exit fullscreen mode

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>"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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")
})
Enter fullscreen mode Exit fullscreen mode

F#

let data = Some(50)

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

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

Mutable Keyword

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

let mutable data = 6
data <- 8
Enter fullscreen mode Exit fullscreen mode

Yield Keyword

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

Tuples

let myTuple = (5, "hello")

let typedTuple: int * string = (5, "hello")
Enter fullscreen mode Exit fullscreen mode

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

Oldest comments (16)

Collapse
 
johannesvollmer profile image
Johannes Vollmer

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

Collapse
 
rametta profile image
Jason

Glad you liked it 🙂

Collapse
 
gordonbgood profile image
GordonBGood

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.

Collapse
 
davidchase profile image
David Chase

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

Collapse
 
rametta profile image
Jason

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.

Collapse
 
tonyvca profile image
Tony Henrique

Very nice!

Collapse
 
gordonbgood profile image
GordonBGood • Edited

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.

Collapse
 
rametta profile image
Jason

Thanks for the feedback Gordon

Collapse
 
vekzdran profile image
Vedran Mandić

Wow fantastic addition! Thank you.

Collapse
 
natelkins profile image
Nat Elkins

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

Collapse
 
scotthutchinson profile image
Scott Hutchinson

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.

Collapse
 
vekzdran profile image
Vedran Mandić

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.

Collapse
 
rametta profile image
Jason

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

Collapse
 
saint4eva profile image
saint4eva

Nice article. Thank you for sharing.

Collapse
 
charlesroddie profile image
Charles Roddie

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

Collapse
 
rametta profile image
Jason • Edited

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.