Write your own Excel in 100 lines of F# - Tomas Petricek
TP
Write your own Excel in 100 lines of F#
I've been teaching F# for over seven years now, both in the public F# FastTrack course that we run<br>at SkillsMatter in London and in various custom trainings for private companies. Every time I teach<br>the F# FastTrack course, I modify the material in one way or another. I wrote about some of this<br>interesting history last year in an fsharpWorks article. The course now has a stable half-day<br>introduction to the language and a stable focus on the ideas behind functional-first programming,<br>but there are always new examples and applications that illustrate this style of programming.
When we started, we mostly focused on teaching functional programming concepts that might be useful<br>even if you use C# and on building analytical components that your could integrate into a larger<br>.NET solution. Since then, the F# community has matured, established the F# Software Foundation,<br>but also built a number of mature end-to-end ecosystems that you can rely on such as Fable,<br>the F# to JavaScript compiler, and SAFE Stack for full-stack web development.
For the upcoming December course in London, I added a number of demos and hands-on tasks built<br>using Fable, partly because running F# in a browser is an easy way to illustrate many concepts<br>and partly because Fable has some amazing functional-first libraries.
If you are interested in learning F# and attending our course, the next F# FastTrack<br>takes place on 6-7 December in London at SkillsMatter. We also offer custom<br>on-site trainings. Get in touch at @tomaspetricek<br>or email tomas@tomasp.net for a 10% discount for the course.
One of the new samples I want to show, which I also live coded at NDC 2018,<br>is building a simple web-based Excel-like spreadsheet application. The spreadsheet demonstrates<br>all the great F# features such as domain modeling with types, the power of compositionality<br>and also how functional-first approach can be amazingly powerful for building user interfaces.
What is a spreadsheet?
The sample compiles to JavaScript, so the best way of explaining what we want to build is<br>to give you a live demo you can play with! Since this is a blog post about functional programming,<br>I already implemented both Fibonacci numbers (column B) and factorial (column D) in the spreadsheet for you!
You can click on any cell to edit the cells. To confirm your edit, just click on any other cell.<br>You can enter numbers such as 1 (in cell B1) or formulas such as =B1+B2 in cell B3. Formulas<br>support parentheses and four standard numerical operators. When you make an edit, the spreadsheet<br>automatically updates. If you make a syntax error, reference empty cell or create a recursive<br>reference, the spreadsheet will show #ERR.
Full source code is available in my elmish-spreadsheet repository on GitHub<br>(as a hands-on exercise in master branch and fully working in the completed branch), but you<br>can also play with it in the Fable REPL (see Samples, Elmish, Spreadsheet),<br>which lets you edit and run F# in the browser.
Defining the domain model
Following the typical F# type-driven development style, the first thing we need to think about<br>is the domain model. Our types should capture what we work with in a spreadsheet application.<br>In our case, we have positions such as A5 or C10, expressions such as =A1+3 and the sheet<br>itself which has user input in some of the cells. To model these, we define types for Position,<br>Expr and Sheet:
1:<br>2:<br>3:<br>4:<br>5:<br>6:<br>7:<br>8:
type Position = char * int
type Expr =<br>| Number of int<br>| Reference of Position<br>| Binary of Expr * char * Expr
type Sheet = MapPosition, string>
A Position is simply a pair of column name and a number. An expression is more interesting,<br>because it is recursive. For example, A1+3 is an application of a binary operator on sub-expressions<br>A1, which is a reference and 3 which is a numerical constant. In F#, we capture this nicely<br>using a discriminated union. In the Binary case, the left and right sub-expressions are themselves<br>values of the Expr type, so our Expr type is recursive.
The type Sheet is a map from positions to raw user inputs. We could also store parsed expressions or<br>even evaluated results, but we always need the original input so that the user can edit it. To make<br>things simple, we'll just store the original input and parse it each time we need to evaluate the<br>value of a cell. To do the parsing and evaluation, we'll later define two functions:
1:<br>2:
val parse : string -> Expr option<br>val evaluate : Expr * Sheet -> int option
We will talk about these later when we discuss the logic behind our spreadsheet, but writing the<br>type down early is useful. Given these types, we can already see how everything fits together.<br>Given a position, we can do a lookup into Sheet to find the entered text, then we can parse it<br>using parse to get Expr and, finally, pass the expression to evaluate to get the resulting<br>value. We also see that both...