Bitwise and List

Allergies
Allergies in Elm
allergies : List Allergy
allergies =
    [ Eggs
    , Peanuts
    , Shellfish
    , Strawberries
    , Tomatoes
    , Chocolate
    , Pollen
    , Cats
    ]

toList : Int -> List Allergy
toList score =
  allergies
    |> List.indexedMap (\i allergy -> ( 2 ^ i, allergy ))
    |> List.filter (\( allergyScore, _ ) -> Bitwise.and allergyScore score > 0)
    |> List.map Tuple.second

isAllergicTo : Allergy -> Int -> Bool
isAllergicTo allergy score =
  toList score |> List.member allergy    

In this approach

This approach implicitly uses the index of an item in a list to represent the bit position (minus one) for the allergy score.

List.indexedMap is used to map the list and provide the index of each item, it retains the Allergy and adds the bit mask in a tuple. List.filter then filters out any Allergy in the list where the bit mask doesn't match the score using Bitwise.and. List.map and Tuple.second then map the filtered list from Tuple back to Allergy.

You can see the Bitwise and List solution on exercism.

When to use this approach?

This code is idiomatic in Elm and is concise. It is probably the solution many Elm developers would naturally end up with first.

This approach does not fully embrace the domain concept of using bit positions in the allergy score to represent a list of Allergy. It is relatively easy to work out what the code is doing, but it is not as easy to guess why it is doing it, you have to look at the exercise instructions to understand. Ideally the code should communicate its meaning to you.

toList is a relatively expensive operation, iterating the allergies three times, or O(3n). The first is to add the index, the second is to filter and the third is to map. We can't easily use List.foldr to avoid this, as it doesn't provide the list index, and there is no indexedFoldr function available from the core libraries. You could however use (index, list) for the accumulator in List.foldr to keep track of the index, at the expense of a little extra complexity. isAllergicTo adds another iteration over the result of toList, making it more expensive. However the initial allergies list is known and of small length, so for this exercise we should not try to prematurely optimise.

The compiler does not guarantee that the allergies list contains all the Allergy types. You can use the type iterator pattern or use the no missing type constructor rule in Elm Review to fix this.

This approach guarantees all bit positions are valid, unless the number of allergies becomes too big, as Javascript (Elm's compilation target) bitwise operators only operate on 32 bit integers.

This approach also guarantees that all bit positions are sequential. This is potentially useful, but means that if there was ever a need for non sequential bit positions, we would have to refactor.

6th Nov 2024 · Found it useful?