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.