Skip to content

lue-bird/elm-and-or

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

both or only one of them

The basic types of this package look like this:

type AndOr first second
    = Both ( first, second )
    | Only (Or first second)

type Or first second
    = First first
    | Second second

Both types can be quite useful! ...But first

❌ misuse-cases

AndOr and Or are prone to misuse, just like Maybe, tuple, Bool etc.

When there is a representation of your data that's more descriptive, use that instead!

Example: Modeling the result of an operation that can fail, succeed or succeed with warnings

type alias Fallible a =
    AndOr a Error

This is problematic from multiple standpoints:

  • Why should error and warning types be required to be the same?
  • Why not have a unified case for success:
    type alias Fallible a =
        Or { result : a, warnings : List RecoverableError }
           UnrecoverableError
  • Who tells you that "first" means "success" and "second" means "error"? (or the other way round?)
  • Why not use descriptive names:
    type Fallible a
        = Success { result : a, errors : List RecoverableError }
        | UnrecoverableError UnrecoverableError

✔️ use-cases

AndOr and Or are useful for generic operations where no descriptive names exist – similar to how tuples are used for partition results because there is no information on what each side means.

  • generic diffs
    • see KeysSet.fold2From which is similar to Dict.merge but more comprehensible and easier to work with thanks to AndOr
  • map2 where overflow elements aren't disregarded:
    List.AndOr.map2 : (AndOr a b -> c) -> ( List a, List b ) -> List c
    List.AndOr.map2 combine lists =
        case lists of
            ( firsts, [] ) ->
                firsts
                    |> List.map
                        (\first -> Only (Or.First first) |> combine)
            
            ( [], seconds ) ->
                seconds
                    |> List.map
                        (\first -> Only (Or.Second first) |> combine)
            
            ( firstHead :: firstTail, secondHead :: secondTail ) ->
                (AndOr.Both firstHead secondHead |> combine)
                  :: List.AndOr.map2 combine firstTail secondTail
  • type-safe partitioning
    List.Or.partition : (a -> Or first second) -> (List a -> ( List first, List second ))
    List.Or.partition chooseSide list =
        case list of
            [] ->
                []
            
            head :: tail ->
                let
                    consHead : ( List first, List second ) -> ( List first, List second )
                    consHead =
                        case head |> chooseSide of
                            First first ->
                                And.firstMap ((::) first)
                            Second second ->
                                And.secondMap ((::) second)
                in
                tail |> List.Or.partition chooseSide |> consHead

prior art – AndOr

All of them don't separate out the "only one of both" case.

prior art – Or

All of them use Left and Right as variant names whereas Or's First and Second are consistent with elm's tuple part names.

suggestions?

The API is pretty slim, only covering the needs I could think of. If you have an idea for a useful helper → contribute