core-extra icon indicating copy to clipboard operation
core-extra copied to clipboard

Add Result.Extra.combineFoldl

Open miniBill opened this issue 1 year ago • 8 comments

combineFoldl

combineFoldl :
    (a -> acc -> Result err acc)
    -> acc
    -> List a
    -> Result err acc
combineFoldl f acc list =
    case list of
        [] ->
            Ok acc

        head :: tail ->
            case f head acc of
                Err e ->
                    Err e

                Ok v ->
                    resultFoldl f v tail

Motivating use case

It's common to fold over a list while keeping track of some state. Doing Result.andThen / Result.map is a List.foldl does not short circuit, whereas this function does.

miniBill avatar Jul 23 '24 18:07 miniBill

Why not combineFoldr too?

~~Because it's much harder to implement~~ because you need to traverse the whole list, and at that point might as well use List.foldr

miniBill avatar Jul 23 '24 18:07 miniBill

@gampleman opinions?

miniBill avatar Jul 25 '24 09:07 miniBill

  1. I'd like to see some comparison vs using List.Extra.stoppableFoldl, since that aims at more or less the same use case.

  2. I feel like we should figure out some consistent policy for these sorts of functions if they go into List.Extra or Result/Maybe.Extra. My gut feeling here is that this is more of a List.Extra thing, but can't really articulate a coherent argument either way.

  3. Can you explain your rationale for the naming? I'm not really seeing much of a commonality between this and the other Result.Extra.combine* functions...

gampleman avatar Jul 25 '24 09:07 gampleman

  1. It's very similar, but step is Step a whereas result is Result e x, and the additional type parameter is essential
  2. I'd say this goes in Result.Extra, and an hypothetical combineFoldl with Maybe goes into Maybe.Extra
  3. Terrible name, please help come up with a better one

miniBill avatar Jul 25 '24 13:07 miniBill

It's very similar, but step is Step a whereas result is Result e x, and the additional type parameter is essential

Yeah but Step (Result e x) is what you could quite easily do. So I guess the question is: is it worth adding this extra abstraction? I think having some code examples written with both would be helpful.

gampleman avatar Jul 25 '24 17:07 gampleman

withCombineFoldl : List String -> Result String (Dict String Int)
withCombineFoldl list =
    -- Result.Extra.combineFoldl
    combineFoldl
        (\item acc ->
            case parse item of
                Err e ->
                    Err e

                Ok ( k, v ) ->
                    Ok (Dict.insert k v acc)
        )
        Dict.empty
        list


withStoppableFoldl : List String -> Result String (Dict String Int)
withStoppableFoldl list =
    List.Extra.stoppableFoldl
        (\item acc ->
            case acc of
                Err e ->
                    -- This never happens
                    List.Extra.Stop (Err e)

                Ok okAcc ->
                    case parse item of
                        Err e ->
                            List.Extra.Stop (Err e)

                        Ok ( k, v ) ->
                            List.Extra.Continue (Ok (Dict.insert k v okAcc))
        )
        (Ok Dict.empty)
        list

It's not terrible to be honest. But it's also not great.

miniBill avatar Aug 12 '24 10:08 miniBill

And perhaps like this, which is fairly nice

withCombineFoldl : List String -> Result String (Dict String Int)
withCombineFoldl =
    Result.Extra.combineFoldl
        (\item acc ->
            parse item
                |> Result.map (\( k, v ) -> Dict.insert k v acc)
        )
        Dict.empty

hm OK, I think I'm coming around to this. I'm still not in love with the name. What about foldlUntilError or some such?

gampleman avatar Aug 15 '24 09:08 gampleman

That's not a terrible name!

miniBill avatar Aug 21 '24 14:08 miniBill