Rationale for dropping List.scanl in elm 0.19

I notice that List.scanl has been dropped from elm 0.19. I’m not challenging this decision, but rather I am trying to get a better understanding of the rationale behind the development of elm (e.g. I see a gradual distancing of elm from its Haskell origins).

I realise I can create my own scanl function such as:

scanl : (a -> b -> b) -> b -> List a -> List b
scanl fn b =
    let
        scan a bs =
            case bs of
                hd :: tl ->
                    fn a hd :: bs

                _ ->
                    []

    in
    List.foldl scan [b] >> List.reverse

Personally I find scanl quite useful and I was wondering what the rationale was for dropping it. Is the idea to encourage higher level functions like this be written by users and leave elm with a minimal set of set of more primitive functions? Or something else perhaps?

3 Likes

There is not much rationale given in the commit message that removed it: https://github.com/elm/core/commit/77e5c6711c2f956e14ede5b08a3ae7dbb0464f6c#diff-0cffa339bd6235b9902e9934de9ddd8f

Until Evan tells us, we can only guess. My guess would be that it’s a “niche” function, better fit for elm-community/list-extra. (If that’s true, the question is… do the maintainers know? :stuck_out_tongue: and do they want to fill the gap?)

cc @Chadtech

It is already in elm-community/list-extra:

https://package.elm-lang.org/packages/elm-community/list-extra/latest/List-Extra#scanl

(introduced by a Updating for 0.19 commit a few months ago).

1 Like

The renaming in this commit with filter and keepIf is strange. But looks like it got reverted at some point :wink:

I should have checked before writing that :slight_smile:

Yeah I was just upgrading list-extra for the alpha, and I was getting a compiler error that List.scanl didnt exist. Since we had other scan functions like scanl1, I thought it would be awkward if scanl was missing, so I just copied and pasted the old scanl implementation into List.Extra.

Regarding keepIf, for just the first alpha version of 0.19, take was renamed keep. Thats why I changed takeIf to keepIf and then reverted it again when the alpha progressed.

Dead code elimination should make “niche” functions less of a concern in 0.19. In fact, you could ask why we even need a list-extra any more?

Keeping the standard library small and easy to learn is also a good reason to keep some more rare and less intuitive functions in list-extra.

It seems to me that the selection of functions that make it into the standard library is an opportunity not simply to provide particular functionality, but to send a message that the approach they embody is explicitly encouraged. i.e. it can signal an ‘elm way of doing things’.

This is partly why I was slightly surprised to see scanl dropped, as to my (relatively new) understanding of functional programming, it offers a concise and elegant approach to sequential list processing. Its conceptual link with folding makes it easy to understand and to me at least, doesn’t feel particularly ‘niche’ or ‘rare’.

I raised this issue because I wondered if I am missing something, an ‘elm approach’ which makes scanning redundant or misaligned with a more favoured approach. As an analogy, I can see why Haskell’s list comprehension is not available in elm, instead encouraging map and filter to provide a clearer and arguably more robust approach.

3 Likes

I must confess I used scanl maybe twice in my life. Maybe I’m using other solutions where scanl would be better?

Can you share examples of the problems you’d use scanl for?

I’m in the same boat as @Janiczek, actually. I don’t think I’ve ever actually used it :sweat_smile:

Basically, any use case of foldl where you also want the intermediate results.

If you want the sum of a list of numbers, you can write:

sum : List number -> number 
sum = List.foldl (+) 0 

Substituting scanl gives you the cumulative sum, i.e. the list of intermediate sums:

cumulativeSum : List number -> List number 
cumulativeSum  = 
    scanl (+) 0


sum [1,2,3]
>> 6

cumulativeSum [1,2,3]
>> [0, 1, 3, 6]

I don’t think (nor expect) that my response will change anything here, but I’d like to tell everyone that I’ve used and use scanl regularly in my code (in both Elm, Elixir and Haskell), and frequently point out its existence to other people, since it happens often that people either ‘roll their own’ since its use case is rather common.

scanl is one of the functions that makes working in a functional way with lists simple, rather than complex. I’ve seen multiple people new to functional programming be stuck on their code since they did not know how to do something which is commonly done using a loop in an imperative (and possibly object-oriented) language.

I think that having these kinds of ‘Look, working with lists does not have to be a hassle’-functions in a place that is highly visible for newcomers is valuable for understanding Elm and functional programming in general.

1 Like

To expand on the examples above I find scanluseful when you are performing sequential actions on a list but then need to extract some value from the processed list that might be anywhere within it. In my field, geometric problems can often fall into this category.

@Janiczek, I know you are a fan of Advent of Code, so a good example is Day 11, 2017, where it was necessary to find furthest distance away from a start point along the trajectory of a random walk. See my solution in Elm that uses scanl for part 2 (and compare with foldl in part 1):

1 Like

I should add, scanning is often more easily applied in lazy list evaluation where you need to cumulatively process a list until some condition is met. This may explain why it is more common in lazy languages like Haskell than eager ones like Elm, but nevertheless, as @Qqwy says, I find it a helpful signpost towards concise functional solutions to list processing.

Yeah, Advent of Code might be majority of the usages for scanl that I had :sweat_smile:

I understand what it does compared to foldl yet I still didn’t use it in my actual application/library code. I’m not sure puzzles count much.

On the other hand, I can see myself using it in Haskell with the infinite lists + dropWhile / takeWhile combo. These things (I’m sure there are others that it’s great for) aren’t of much use in Elm without explicitly using Stream or LazyList or something else of the infinite nature.

It still seems to me like scanl on the List type is niche and thus OK to have in List.Extra. I’d still like to see examples of usage in Elm, to compare with my “how would I implement this” instincts.

For me as an academic doing data science and visualization, “puzzles” form a large part of my coding (real problems, not just Advent of Code style fun puzzles). As I mentioned above, spatial trajectory analysis can make good use of scanning.

But that aside, here is one simple example of using scanl to generate data for CUSUM charts which I use quite often in data analysis:

cusum : Float -> List Float -> List Float
cusum baseline =
    List.scanl (\x acc -> acc + (x - baseline)) 0
        >> List.drop 1

The implementation is very simple in this example, but scanl is a natural fit for this kind of thing. Here’s the result.

More generally, in visualisation we often need to see evolving sequences rather than simply the final result of a sequence of operations on data, an scanning operations can be useful here.

4 Likes

Thanks for the real-world example!

Very interesting, and highly relevant to my day job - thanks for posting.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.