Safe dead code removal and compile-time laziness

· Allanderek's blog

#elm

Jeroen Engels of elm-review and elm-radio fame has written an excellent blog post regarding the safe removal of dead code in a purely functional language. The main take-away is that because there are no side-effects, all code dependencies are explicit. Because of this it's relatively easy to determine that code does not depend on other code, and therefore some code is dead (ie. unused), and can be safely removed.

You can extend this idea, and say the order that code is executed in, is only dependent on the explicit dependencies between code. Two days ago I wrote about compile-time laziness, I showed that the following code:

 1showCouponCode : Coupon -> Discounts -> Html msg
 2showCouponCode coupon discounts =
 3    let
 4        couponIsEmpty =
 5            String.isEmpty coupon
 6        couponIsUsed =
 7            List.any (\discount -> discount.code == coupon) discounts
 8    in
 9    case couponIsEmpty of
10        True ->
11            Html.nothing
12        False ->
13            case couponIsUsed of
14                True ->
15                    Html.div
16                        [ Attributes.class "used-coupon-code" ]
17                        [ Html.text coupon
18                        , Icon.tick
19                        ]
20                False ->
21                    Html.div
22                        [ Attributes.class "unused-coupon-code" ]
23                        [ Html.text coupon
24                        , Icon.cross
25                        ]
26

can be re-written as the following:

 1showCouponCode : Coupon -> Discounts -> Html msg
 2showCouponCode coupon discounts =
 3    let
 4        couponIsEmpty =
 5            String.isEmpty coupon
 6    in
 7    case couponIsEmpty of
 8        True ->
 9            Html.nothing
10        False ->
11            let
12                couponIsUsed =
13                    List.any (\discount -> discount.code == coupon) discounts
14            in
15            case couponIsUsed of
16                True ->
17                    Html.div
18                        [ Attributes.class "used-coupon-code" ]
19                        [ Html.text coupon
20                        , Icon.tick
21                        ]
22                False ->
23                    Html.div
24                        [ Attributes.class "unused-coupon-code" ]
25                        [ Html.text coupon
26                        , Icon.cross
27                        ]
28

This is a kind of generalisation of Jeroen's point. In this case, couponIsUsed cannot be removed entirely because there is code that still depends upon it. However it can be moved because we know that there are no implicit dependencies on when it is evaluated. This is powerful. It allows for some pretty interesting transformations. A commonly acknowledged one is called 'fusion'. It allows you to avoid some intermediate structures, and/or traverse a structure less often. Consider:

1users
2    |> List.map getMainEmail
3    |> List.map displayEmail

This creates an intermediate list, the result of the first List.map call, and also traverses that list. We can avoid both by doing the following:

1users
2    |> List.map (getMainEmail >> displayEmail)

Note that this changes the execution order. The first version would execute all getMainEmail function invocations before then doing all of the displayEmail invocations. The second version intersperses them. However, because you're using a purely functional language you know that this transformation must be safe, whereby safe we mean results in the same output for any given input.

In the post two days ago I suggested an elm-to-elm transformation tool. One question, given that elm-review allows for automated fixes, could we implement such a tool using the excellent code-base of elm-review? We could start by a tool that moves definitions to their inner-most scope, something that often improves the readability/maintainability of the code as well as the performance.