Skip to content

Instantly share code, notes, and snippets.

@gampleman
Created March 30, 2018 09:44
Show Gist options
  • Save gampleman/5c9dde354c08cd569c901da639a0f258 to your computer and use it in GitHub Desktop.
Save gampleman/5c9dde354c08cd569c901da639a0f258 to your computer and use it in GitHub Desktop.
A module that abstracts over common collection types
module Collection
exposing
( Collection
, isEmpty
, length
, head
, tail
, take
, drop
, slice
, filter
, empty
, singleton
, append
, concat
, map
, indexedMap
, foldl
, foldr
, fromList
, toList
, fromArray
, toArray
, fromMaybe
)
{-| Collection is an abstraction over individual collection data structures. If you can't decide
whether to use a List or an Array -- well, now you don't have to.
In general this module will simply dispatch function calls to the underlying data structure, hence the
overhead should be quite minimal. There are a few exceptions, notably around `append`, which when given
a list and an array will need to pick one of these. From an API standpoint, this is invisible to you,
but there might be performance implications to this.
If you find particular functions missing from this module, it may well be an indication that you actually
need a particular collection type rather than this common subset.
Note also that we can easily add support for further collections in a minor update and you code will now magically
support these!
# Creating a collection
@docs fromList, fromArray, fromMaybe, singleton, empty
# Basics
@docs isEmpty, length, append
# Taking collections appart
@docs head, tail, take, drop, slice, filter, toList, toArray
# Mapping, folding
@docs map, indexedMap, foldl, foldr, concat
-}
import Array exposing (Array)
type Collection a
= L (List a)
| A (Array a)
{-| Determine if a collection is empty.
isEmpty (fromList [])
--> True
-}
isEmpty : Collection a -> Bool
isEmpty c =
case c of
L list ->
List.isEmpty list
A array ->
Array.isEmpty array
{-| Determine the length of a collection.
length (fromList [1,2,3])
--> 3
-}
length : Collection a -> Int
length c =
case c of
L list ->
List.length list
A array ->
Array.length array
{-| Extract the first element of a collection.
head (fromList [1,2,3])
--> Just 1
head (fromList [])
--> Nothing
-}
head : Collection a -> Maybe a
head c =
case c of
L list ->
List.head list
A array ->
Array.get 0 array
{-| Extract the rest of the collection.
tail (fromList [1,2,3])
--> Just [2,3]
tail (fromList [])
--> Nothing
-}
tail : Collection a -> Maybe (Collection a)
tail c =
case c of
L list ->
List.tail list |> Maybe.map L
A array ->
let
l =
Array.length array
in
if l > 1 then
Array.slice 1 l array |> A |> Just
else
Nothing
{-| Keep only elements that satisfy the predicate.
filter isEven (fromList [1,2,3,4,5,6]) == [2,4,6]
-}
filter : (a -> Bool) -> Collection a -> Collection a
filter p c =
case c of
L list ->
L (List.filter p list)
A array ->
A (Array.filter p array)
{-| Take the first n members of a collection.
take 2 (fromList [1,2,3,4])
--> fromList [1,2]
-}
take : Int -> Collection a -> Collection a
take count c =
case c of
L list ->
L <| List.take count list
A array ->
Array.slice 0 count array |> A
{-| Drop the first n members of a collection.
drop 2 (fromList [1,2,3,4])
--> fromList [3,4]
-}
drop : Int -> Collection a -> Collection a
drop count c =
case c of
L list ->
L <| List.drop count list
A array ->
Array.slice count (Array.length array) array |> A
{-| Get a sub-section of an collection: `(slice start end collection)`. The start is a zero-based
index where we will start our slice. The end is a zero-based index that indicates
the end of the slice. The slice extracts up to but not including end.
slice 0 3 (fromList [0,1,2,3,4])
--> fromList [0,1,2]
slice 1 4 (fromList [0,1,2,3,4])
--> fromList [1,2,3]
Both the start and end indexes can be negative, indicating an offset from the end of the collection.
slice 1 -1 (fromList [0,1,2,3,4])
--> fromList [1,2,3]
slice -2 5 (fromList [0,1,2,3,4])
--> fromList [3,4]
This makes it pretty easy to `pop` the last element off of an collection: `slice 0 -1 collection`
-}
slice : Int -> Int -> Collection a -> Collection a
slice s e c =
case c of
L list ->
let
l =
List.length list
ns =
if s < 0 then
l + s
else
s
ne =
if e < 0 then
l + e
else
e
in
if ns > ne then
L []
else
L (listSlice 0 ns ne list [])
A array ->
Array.slice s e array |> A
listSlice : Int -> Int -> Int -> List a -> List a -> List a
listSlice current start end list result =
case list of
[] ->
List.reverse result
x :: xs ->
if current < start then
listSlice (current + 1) start end xs result
else if current < end then
listSlice (current + 1) start end xs (x :: result)
else
List.reverse result
{-| Create an empty collection
-}
empty : Collection a
empty =
L []
{-| Create a collection with only one element.
-}
singleton : a -> Collection a
singleton =
List.singleton >> L
{-| Put two collections together.
-}
append : Collection a -> Collection a -> Collection a
append a b =
case ( a, b ) of
( L al, L bl ) ->
L (List.append al bl)
( A aa, A ba ) ->
A (Array.append aa ba)
-- See https://ellie-app.com/jwkv6n9VBa1/0
( L al, A ba ) ->
A (Array.append (Array.fromList al) ba)
( A aa, L bl ) ->
A (Array.append aa (Array.fromList bl))
{-| Concatenate a bunch of lists into a single list:
concat (fromList [fromList [1,2], fromList [3], fromList [4,5]])
--> fromList [1,2,3,4,5]
-}
concat : Collection (Collection a) -> Collection a
concat =
foldr append empty
{-| Apply a function to every element of a collection.
map sqrt (fromList [1,4,9])
--> fromList [1,2,3]
map not (fromList [True,False,True])
--> fromList [False,True,False]
-}
map : (a -> b) -> Collection a -> Collection b
map f c =
case c of
L list ->
L (List.map f list)
A array ->
A (Array.map f array)
{-| Apply a function on every element with its index as first argument.
indexedMap (*) (fromList [5,5,5])
--> fromList [0,5,10]
-}
indexedMap : (Int -> a -> b) -> Collection a -> Collection b
indexedMap f c =
case c of
L list ->
L (List.indexedMap f list)
A array ->
A (Array.indexedMap f array)
{-| Reduce a collection from the left. Read foldl as “fold from the left”.
foldl (::) [] (fromList [1,2,3])
--> [3,2,1]
-}
foldl : (a -> b -> b) -> b -> Collection a -> b
foldl f d c =
case c of
L list ->
List.foldl f d list
A array ->
Array.foldl f d array
{-| Reduce a collection from the right. Read foldl as “fold from the right”.
foldr (+) [] (fromList [1,2,3])
--> 6
-}
foldr : (a -> b -> b) -> b -> Collection a -> b
foldr f d c =
case c of
L list ->
List.foldr f d list
A array ->
Array.foldr f d array
{-| Convert the collection to a list.
-}
toList : Collection a -> List a
toList c =
case c of
L list ->
list
A array ->
Array.toList array
{-| Convert the collection to an array.
-}
toArray : Collection a -> Array a
toArray c =
case c of
L list ->
Array.fromList list
A array ->
array
{-| Create a collection from a list
-}
fromList : List a -> Collection a
fromList =
L
{-| Create a collection from an array
-}
fromArray : Array a -> Collection a
fromArray =
A
{-| Create a collection from a maybe
The inverse (i.e. `toMaybe`) is called `head`.
-}
fromMaybe : Maybe a -> Collection a
fromMaybe m =
case m of
Just a ->
singleton a
Nothing ->
empty
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment