Type Driven Development: Simple masonry layout in 50 lines of Elm code
Tags: elm, webdev, functional, tutorial
Published: 2020-03-14 05:42:12

Demo: http://elm-masonry.surge.sh/

Code: https://github.com/lucamug/elm-masonry/

Code in the browser: https://ellie-app.com/8j8XdXn7xHda1

The final result:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mya9hganfkhovioa6r54.gif)

A __Masonry layout__ is a way to fit together elements of possibly different sizes without gaps.

There are many different way to implement masonry layout, using [CSS and/or Javascript](https://css-tricks.com/piecing-together-approaches-for-a-css-masonry-layout/). It is a kind of [bin packing problem](https://en.wikipedia.org/wiki/Bin_packing_problem).

The method that we will use in this tutorial is simple. Adding elements from left to right in a set of columns. Each element is added in the column that has shorter height.

We will talk a lot about [`fold`](https://en.wikipedia.org/wiki/Fold_(higher-order_function)) (or `reduce` in other languages). So have a look at their definition before keep reading.

We will also let the types of our functions to drive our development in an attempt of what is called _Type Driven Development_.

# Let’s start digging in the code!

We want to transform a list of heights into a two dimensional matrix that represent the columns of the final layout:

So from this data:
```Elm
items = [ 250, 200, 300, 200, 450, 200, 300, 500, 300, 250, 200, 150, 200 ]
columns = 4
```
to this matrix:
```Elm
[ [ (11, 150 ), ( 8, 300 ), ( 5, 200 ), ( 3, 200 ) ]    X
, [                         ( 7, 500 ), ( 2, 300 ) ]    ^
, [             (10, 200 ), ( 4, 450 ), ( 1, 200 ) ]    │
, [ (12, 200 ), ( 9, 250 ), ( 6, 300 ), ( 0, 250 ) ]    │
]                                                       │
                                          Y <───────────┼
```

That will show up flipped on screen:

```Elm
╔════════════════════════════════════════════════════╗
║ (  0, 250 )  (  1, 200 )  (  2, 300 )  (  3, 200 ) ║
║ (  6, 300 )  (  4, 450 )  (  7, 500 )  (  5, 200 ) ║
║ (  9, 250 )  ( 10, 200 )               (  8, 300 ) ║
║ ( 12, 200 )                            ( 11, 150 ) ║
╚════════════════════════════════════════════════════╝
```
the first value of the tuples in the matrix is the position in the initial list, the second is the height.

Note that the matrix is filled from the bottom right with element 0 and that rows in the matrix will be columns in the layout.

The idea is that each item will be added to the matrix row that has a smaller value, where the value is the sum of all items already present in the row.

This is a progression of the matrix creation:

The first four items are added to each row because the sum is equal zero:

```Elm
[ [               ( 3, 200 ) ]
, [               ( 2, 300 ) ]
, [               ( 1, 200 ) ]
, [               ( 0, 250 ) ]
]
```

The fifth item will go in the second row from bottom because it sum is the lower (200), together with the fourth row.

```Elm
[ [               ( 3, 200 ) ]
, [               ( 2, 300 ) ]
, [   ( 4, 450 ), ( 1, 200 ) ]
, [               ( 0, 250 ) ]
]
```

The fourth row is the next to be occupied

```Elm
[ [   ( 5, 200 ), ( 3, 200 ) ]
, [               ( 2, 300 ) ]
, [   ( 4, 450 ), ( 1, 200 ) ]
, [               ( 0, 250 ) ]
]
```

Now the row with the minimum sum is the second (250):

```Elm
[ [   ( 5, 200 ), ( 3, 200 ) ]
, [               ( 2, 300 ) ]
, [   ( 4, 450 ), ( 1, 200 ) ]
, [   ( 6, 300 ), ( 0, 250 ) ]
]
```

…and so one until all the items are included.

As a starter let’s define some alias of types so that type signature would be meaningful.

```Elm
type alias Position =
    Int

type alias Height =
    Int

type alias Masonry =
    List (List ( Position, Height ))
```

Nothing new here. Let’s plan the algorithm only using type signatures.
The end result is a function that given a list of `heights` and a `number of columns` would return a Masonry. Let’s call it fromItems:

```Elm
fromItems : List Height -> Int -> Masonry
```

Because we need to keep track of the position of the Heights, [`List.Extra.indexedFoldl`](https://package.elm-lang.org/packages/elm-community/list-extra/latest/List-Extra#indexedFoldl) seems a good candidate for this job.
The type signature of `indexedFoldl` is

```Elm
indexedFoldl : (Int -> a -> b -> b) -> b -> List a -> b
```

putting these two signatures together we obtain:

```Elm
a = Height

b = Masonry
```

so indexedFoldl became

```Elm
indexedFoldl : (Int -> Height -> Masonry -> Masonry) -> Masonry -> List Height -> Masonry
```

we need now an helper function of the type

```Elm
Int -> a -> b -> b
```

equivalent to

```Elm
Position -> Height -> Masonry -> Masonry
```

this function add the `Item (Position, Height)` to the `Masonry` so let’s call it `addItemToMasonry`.

It will be called for every Item so the Masonry will grow gradually the way we explained at the beginning of he post.

Before adding an Item we need to find the column with smallest height.

We need a function with this type signature:

```Elm
Masonry -> Position
```

Let’s call this function `minimumHeightPosition` and let’s split this function in two smaller functions with these type signature:

```Elm
Masonry -> List Height

List Height -> Position
```

The first function transforms a Masonry in a list of heights, one height per column (`columnsHeights`)

The second function loops on this list and return the position of the smallest column, using the already used `indexedFoldl` (`positionOfShortestHeight`)

Done! This is the simple outcome of the script:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kozzbilvq9yl4lnii995.png)

You can find the final code at https://github.com/lucamug/elm-masonry/blob/master/src/Masonry.elm

Code in the browser: https://ellie-app.com/8j8XdXn7xHda1

This is a simple result: http://elm-masonry.surge.sh/simple.html

This is a demo that showcases the possibilities of the masonry script: http://elm-masonry.surge.sh/

This post was originally [posted in Medium](https://medium.com/@l.mugnaini/simple-masonry-layout-in-50-lines-of-elm-code-304ea9e9475c).