F# for Excel addicts🎄

jkone27
13 min readDec 17, 2023

Your way out from an Excel sheet💩 job into your nice holiday times 🌞

an F# dragon dev, ready for XMAS holidays with his nice new red coat, sipping tomato juice

Package Manager (recommended) 📦

To be able to easily install and remove software on your computer. Not strictly essential, but you usually need a package manager. Linux came first with this idea, but here is how you can install others in Windows (winget or choco, but I’d stick with winget) and Mac also (homebrew).

time to complete: 5 minutes.

Install Dotnet 🌻

  • without a terminal, if you didn’t install a package manager: here.
  • macOS: brew install dotnet
  • win: winget install Microsoft.DotNet.SDK.8
  • Linux: apt install dotnet or variants for apk.

Then test all is fine and working in a new terminal window, with:

> dotnet --version

time to complete: 10 seconds.

Terminal (PS, optional but also awesome) 🐠

The most basic tool in programming, dating back to the 60s, is the terminal. Sounds strange but even nowadays is still in use, not as much, but can always come in handy. To have a common experience on all operating systems, you could install, is a great terminal that works the same across operating systems (Win, macOS, Linux).

Now that you have dotnet, you can install it in one shot!

> dotnet tool install --global PowerShell

and you can test from your terminal, it with

> ps --version

time to complete: 30 seconds.

Editor 😺

The first thing you need to code in any programming language is a code editor that helps you, it’s kind of like Word or Docs, just so this is what you can use no matter what operating system you are running, use Visual Studio code, it supports tons of languages, the most widely used editor in general and it’s free to use and awesome.

You can easily install it with your package manager, that’s the recommended way over the manual installation process.

Once you can open and check VsCode out, there is plenty of tutorial for its basic usage, but now you should go to the extensions button, and install Ionide or clicking here.

This extension is required to use F# Programming Language, a wonderful and easy-to-use programming language, in my opinion, the best to get you started in dotnet (an ecosystem of languages). See at the end for why F#.

time to complete: 5 minutes.

A Blank Sheet 🐉

an empty Sheet 👌

When you start programming you create a script file with the extension .fsx, that’s like a new blank sheet. Nothing has been filled in yet. you can use the terminal or just do it directly from the code editor (vscode) or in any other way you prefer.

> touch myscript.fsx

time to complete: 30 seconds.

An empty script file, a “program” to be…

A “Temp” Sheet (the REPL) ☯️

you can also open a terminal from visual studio code, click alt/option + J to toggle panel

F# also supports interactive evaluation, so our “sheet file” can also be just a runtime sketch, where we do not save any file yet! a bit like if we had a temp Excel sheet or a temporary table in a database, by entering in REPL mode (like Python). A bit like playing around without ever pressing the save button, this can be useful for testing ideas quickly.

The REPL mode is achieved by running this command on the terminal, without arguments.

> dotnet fsi

time to complete: 30 seconds.

A Cell 👮

a cell A1, set to 1

the smallest thing you can do is assign a value to a variable, that’s exactly like a CELL in a sheet.

let a = 1

> a;;

the special it cell in the REPL contains the last result of your evaluation:

> it;;

time to complete: 30 seconds.

Sending Commands in the console 🚨

An expression can be sent to the REPL by selecting it all in your code editor (vscode) and then hitting alt/option + enter. You can also always type full expressions in the REPL as well if you prefer, e.g.

> 1 + 1;;
> let x = "hello";;

Running the SHEET!🏃‍♀️

running in vscode or in REPL with alt+ENTER

In Excel sheets and the like we have no concept of running a sheet, because, well, what you see is what you get. But, in programming, we do. The reason and motivation behind this is that most of the time, we don’t plug all the values in our program beforehand, but we use script arguments!!!! We will see later, for now, trust me.

In your editor (vscode + Ionide) you can usually just press the green play ▶️ triangular button at the corner of your script, but you can also do the same thing from your terminal:

> dotnet fsi myScript.fsx;;

Printing Values! 📝

When your script executes, the last value in the script will be the result, but if you want, you can still print values in the middle of you script, or wherever you deem useful, with the handy printfn function

printfn $"some value {someVariable}"

REMEMBER ;; in the command line ⚡️

IMPORTANT: ALWAYS use double semicolumns ;; to SEND the command before hitting enter in the F# CLI (REPL), this is because the REPL thinks you want to write more code in the new line or the following lines below if you don’t do that. unless you want to do that of course.

> for i in [1 .. 10 ] do printfn "REMEMBER TO USE SEMICOLUMNS";;

A Function (of 2 Cells) 🎄

using printfn to print some values

To sum 2 cells together, in Excel, we can use the SUM function, taking the reference of two other cells, and summing them in another cell as a result.

summing 2 cells

What we do in functional programming is similar, we bind an expression to a symbol, before we have seen how to bind a single value to a single cell, here is how to bind a function that works on other values, we can call this an expression binding, the cell value of our sumResult cell depends on the individual cell values of a and b.

let a = 1
let b = 2

let sumResult = a + b

printfn $"sum of {a} and {b} is {sumResult}"

> sumResult;;

let’s define a custom function now, to make it simple we can just define our version sum function

let sum a b = a + b
> sum 10 20;;
> sum 1 2;;
> sum -100 +100;;

time to complete: 5 minutes.

Anonymous Single Cell functions 🎅

as an alternative way to create custom functions, we can also use the so-called lambda or arrow functions, they are useful as they can be defined inline, inside other functions as we will see shortly.

let sum = fun a b -> a + b

> sum 1 2;;

A Row ⛵️

Row B

A row in programming is often called a list, collection, sequence, or array. The concept is similar, we have different implementations and performance considerations, but for our use case we can say that a list is sufficient, this is how you declare a list in F#.

In F# semicolumns are optional if you format some entity vertically, as indentation is enough. This is true in general, not only for lists.

let myRow = [ 1; 2; 3 ]

let myRowInVertical =
[
1
2
3
]

time to complete: 5 minutes.

A Row function (Cell by Cell, pull down) 🍰

pulling a single Cell by Cell function in Excel, applies it to all the corresponding rows, matching items 1 by 1

The most common example of mapping function is the map function, unsurprisingly, available in all collection modules, usually Seq is the most general module as all collections implement sequence, so it’s good idea to use it in general. but there might be use cases where you want to use a specific module instead, like List or Array.

> [ 1; 2; 3 ] |> Seq.map (fun a -> a + 1);;

time to complete: 5 minutes.

Filtering 🚭

a filter in Excel

In F# we can directly filter our data with a single function, a filter in the end is, similarly to map, just a function which takes a boolean condition (predicate) anonymous function instead of a mapping anonymous function (mapper or selector):

> [ 1; 2; 3 ] |> Seq.filter (fun a -> a > 1);;

A Rows function (aggregation) 🚵‍♀

summing a range of rows in a single result cell

In Excel we have aggregation functions, that given a set of rows or also on multiple columns, can return a single Cell value. in F# similarly, we have aggregation functions over collections, that return an unique value as result.

> [ 1; 2; 3 ] |> Seq.sum;;

A custom aggregation function can also be defined inline, as a anonymous function using the fun keyword, a common way to define custom generic functions on aggregation is using the fold higher order function.

[ 1; 2; 3 ] |> Seq.fold (fun a n -> a + n + 1) 0 ;;

What we have seen now with Seq.map, Seq.filter, Seq.fold: functions taking other functions or anonymous functions as parameters/arguments, are called higher order functions or HOFs, so don’t be afraid if someone uses the term now! you are becoming an expert already!

time to complete: 5 minutes.

Composing Row Functions (|>) 🚜

a “PIPELINE” in Excel, C=B$+1, D=C$*2, filter D > 4, SUM

In F#, Elixir, and not so many other programming languages is very easy and nice, more than in more common languages like python or javascript, to compose row functions or functions on collections of elements, using the wonderful pipe forward operator |>.

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

Special Custom Row Functions 👽

in Google Sheets custom functions can be defined using JavaScript, this is an example using ES6 generators, in ms Excel usually VBScript or Python can be used

In Excel we also have scripts behind the scenes (usually VBScript but recently also python), to define our custom functions for the most complex scenarios, some similar functionality is given in F# by computation expressions. Some nice computation expressions are already defined for us so that all collection and sequence modules already include list comprehensions computation expressions, like in Python but even more powerful.

let infiniteSequence = 
let mutable i = 0
seq {
while true do
i <- i + 1
yield i
}

> infiniteSequence |> Seq.take(3);;
> infiniteSequence |> Seq.take(100);;

In this example, we created an infinite (because of the while loop) lazy sequence seq, a special kind of lazy collection which can generate its items on demand.

Having Arguments with Your Script! 🐯

Using script arguments is like being able to dynamically assign your input cells with fresh input data whenever it becomes available or you feel like, in 1 go!

What if I don’t want the initial value for some of my cells as: 1, or 2, but I want to decide that later in an easy way. Maybe I want someone else to decide it?

Maybe I want to share my sheet/program with some friend or colleague, and they have to put their own parameters in!

let args = fsi.CommandLineArgs

for arg in args do
printfn $"{arg}"

> dotnet fsi myScript.fsx "hello";;

MAGIC! ⭐️ 🐰

A bit like when some shared sheet gets updated by some other colleague online or by some program, but you can decide any time what the INPUTS of your sheet are going to be!

This is the concept of script parameters. Your magic sheet can also perform I/O (input/output) operations, meaning, talking to your PC, the network, or other connected devices or users! You are becoming a real hacker! we will see how to do some IO later!

Requesting User Inputs 🐧

entering prompt with custom scripts in a google app sheet, similar to having script parameters

in F#, and in many programming languages, requesting a user input from the terminal, is as easy as a simple instruction

> System.Console.ReadLine();;

Template Sheets (packages) 📊

templates in google sheet, made by other people or by google

Very important in programming is to not copy and paste by hand at all times, even though sometimes we do. In general, instead, we use a much smarter way of doing it, called software modules or packages.

A bit like templates for sheets.

You can create your own and you can also import packages made by other people, which is what you will be mostly doing, 99% guaranteed. This is similar to Python pip install or javascript / nodejs npm install.

Here an example of a polyglot library (available in many programming languages) that allows you to quickly plot diagrams!

#r "nuget: XPlot.Plotly"

open XPlot.Plotly

[ 1 .. 10 ] |> Chart.Line |> Chart.Show
the generated diagram on the browser

And you can find tons of packages on Nuget, for .NET apps, or just search in Google appending the Nuget keyword to your search, depending on your needs, or you can install extensions to search for packages in your editor.

Formatting Cells (Value Types) 💴

In excel we can allow input validation by formatting cells. It is very useful in several ways, because we can prevent erroneous values from entering our cells. If we know something is Euros, we don’t want to allow strings to be entered, if we know something is a phone number, we do not want to allow names or strings to be entered in. If we enter emails, would be nice to be able to validate only valid emails.

some common format types (value types) are : int, long, float, double, decimal for numbers, string and charfor text, DateTime or DateOnlyand their combinations as collections…or rows.

Tables (and Custom Formatting) 🏰

a table where the price column is using currency euro formatting, items is a number with no decimal points, and order number is just a string/text

Columns are great, but what if you could shape things in such a way that they would be easier to explain to colleagues or friends, and to validate their inputs in a consistent, shareable and unified way. Wecolme the custom formatting of complex data types, of records and unions.

Imagine a shop, having orders, and having one column for each property of the order, like ordernumber, items, andprice . In F# and other languages we have ways to enforce special conditions on special business tables (records), so that the data entered in them cannot be wrong, and always with their columns (properties or fields) in the right format!

type Amount = { Value: decimal ; Currency: string }

type Order = { OrderNumber: string ; Items: int; Price: Amount }

let orderOne = {
OrderNumber = "ABCDE0001"
Items = 10
Price = {
Value = 200.0m
Currency="EUR"
}
}

// try create orderTwo and orderThree

Tables with Variations (Unions) ⚔️

sometimes in our tables, we have to flag boolean conditions, like yes or no, true or false, but these conditions hide a representation opportunity, a possible simplification…

Now imagine having 3 types of orders, placed orders, shipped orders, and delivered orders. Only shipped and delireved orders have tracking ids, wereas sold orders don’t have any tracking yet. In Excel we can usually express this with extra columns to mark the order as shipped or delivered, and maybe mark an optional column with a NULL or N/A cell? but maybe there is a better way. Maybe they actually different entities, they are evolving in time, depending on the context and customer and business actions on them, and depending on it they become different things with different properties (like a tracking number).

Welcome Discriminated Unions to the rescue, we can eliminate our boolean yes or no columns.

type Amount = { Value: decimal ; Currency: string }

type Order = { OrderNumber: string ; Items: int; Price: Amount }

type TrackingNumber = | Tracking of string
type SalesPrice = | SoldFor of Amount

type BusinessOrder =
| Placed of Order * SalesPrice
| Shipped of Order * SalesPrice * TrackingNumber
| Delivered of Order * SalesPrice * TrackingNumber

More information on discriminated unions and how they work here.

Now you can start with what you have and explore the resto of the world of programming. Have fun!

After thoughts: why is F# easy to learn? 🍾

In the .net language family, is the language closest to many other commonly used ones like python or javascript, much easier to learn than C# or Java. You don’t need to learn object-oriented programming right away, but you can just use “procedural” (but also functional) programming to get started, but without hidden dangers (mutation is allowed, but explicit!).

Functional programming in F# is much similar to:

  • Excel or Google Sheets
  • SQL and declarative programming
  • Everyday Math
  • optimization reasoning/business reasoning, useful to solve real-world problems quickly! collections of items, pipes (|>), transformations, inputs and outputs.
  • python is the easiest and most widely used programming language, because it uses space indentation (not curls), has a REPL and can be used from single script files!
  • javascript, the language of web browsers and frontend development: because you do not need to declare your types upfront, easier than typescript!
  • great support for functions and operations on sets/collections of items, in a very easy way, like Excel, python, or javascript.

--

--