This library provides a type for non-empty lists, called Cons
.
Being able to encode non-emptiness in the type system can lead to simpler, clearer code.
For example, to find the largest element in a List, you have to account for the empty list, which complicates things:
maximum : List comparable -> Maybe comparable
maximum l =
case l of
[] -> Nothing
first::rest -> Just <| List.foldl max first rest
Using Cons, on the other hand, the type system knows the list will never be empty, leading to much simpler code:
maximum : Cons comparable -> comparable
maximum = foldl1 max
A non-empty list of elements of type a
.
cons : a -> List a -> Cons a
A cons with the given head and tail. Equivalent to ::
c = cons 1 [2, 3]
head c == 1
tail c == [2, 3]
uncons : Cons a -> ( a, List a )
The head and tail of the cons.
c = cons 1 [2, 3]
uncons c == (1, [2, 3])
singleton : a -> Cons a
A cons containing only the given element.
c = singleton "a"
toList c == ["a"]
toList : Cons a -> List a
Convert the cons to the equivalent list.
c = cons 1 [2, 3]
toList c == [1, 2, 3]
push : a -> Cons a -> Cons a
A cons with the given head and tail.
Similar to cons, but takes a Cons tail instead of a List.
c = push 1 <| cons 2 [3]
toList c == [1, 2, 3]
Some functions on Lists are forced to use Maybe to handle the empty list. The following functions are quivalent to their List counterparts, but with no need for Maybe.
head : Cons a -> a
The first element of the cons.
c = cons 1 [2, 3]
head c == 1
tail : Cons a -> List a
The list of all elements after the first element of the cons.
c = cons 1 [2, 3]
tail c == [2, 3]
minimum : Cons comparable -> comparable
The smallest element of the cons.
c = cons 1 [2, 3]
minimum c == 1
minimum == foldl1 min
maximum : Cons comparable -> comparable
The largest element of the cons.
c = cons 1 [2, 3]
maximum c == 3
maximum == foldl1 max
Folds over Lists require a start value, but the following fold functions take the start value from the cons.
foldl1 : (a -> a -> a) -> Cons a -> a
Reduce the cons from the left.
c = cons "a" ["b", "c"]
step value result = result ++ value
foldl1 step c == "abc"
foldr1 : (a -> a -> a) -> Cons a -> a
Reduce the cons from the right.
c = cons "a" ["b", "c"]
step value result = result ++ value
foldr1 step c == "cba"
scanl1 : (a -> a -> a) -> Cons a -> Cons a
Reduce the cons from the left, producing a cons of all intermediate results.
c = cons "a" ["b", "c"]
step value result = result ++ value
scanl1 step c == cons "a" ["ab", "abc"]
A cons can't be empty, but a Maybe (Cons a)
can be, if we treat Nothing as empty.
Thus List a
and Maybe (Cons a)
are completely equivalent, and the following functions let you go back and forth between them.
This is useful for recursion on Cons. For example, to recursively find the maximum element of a cons:
maximum : Cons comparable -> comparable
maximum c =
case unconsWithMaybe c of
(first, Nothing) -> first
(first, Just rest) -> max first <| maximum rest
fromList : List a -> Maybe (Cons a)
Convert the list to the equivalent cons, or Nothing for the empty list.
fromList [] == Nothing
fromList [1, 2, 3] == Just <| cons 1 [2, 3]
consWithMaybe : a -> Maybe (Cons a) -> Cons a
A cons with the given head and tail.
c = consWithMaybe "a" Nothing
toList c == ["a"]
d = consWithMaybe 1 <| Just <| consWithMaybe 2 <| Just <| consWithMaybe 3 Nothing
toList d = [1, 2, 3]
unconsWithMaybe : Cons a -> ( a, Maybe (Cons a) )
The head and tail of the cons.
c = consWithMaybe "a" Nothing
unconsWithMaybe c == ("a", Nothing)
d = consWithMaybe 1 <| Just <| consWithMaybe 2 <| Just <| consWithMaybe 3 Nothing
unconsWithMaybe d == (1, Just <| consWithMaybe 2 <| Just <| consWithMaybe 3 Nothing)
maximum : Cons comparable -> comparable
maximum c =
case unconsWithMaybe c of
(first, Nothing) -> first
(first, Just rest) -> max first <| maximum rest
maybeTail : Cons a -> Maybe (Cons a)
The tail of the cons.
c = consWithMaybe "a" Nothing
maybeTail c == Nothing
d = consWithMaybe 1 <| Just <| consWithMaybe 2 <| Just <| consWithMaybe 3 Nothing
maybeTail d == Just <| consWithMaybe 2 <| Just <| consWithMaybe 3 Nothing
length : Cons a -> Int
length c =
case maybeTail c of
Nothing -> 1
Just rest -> 1 + length rest
maybeToList : Maybe (Cons a) -> List a
Convert the cons to the equivalent list, or the empty list for Nothing.
This is the inverse of fromList.
c = fromList []
c == Nothing
maybeToList c == []
c = fromList [1, 2, 3]
c == Just <| cons 1 [2, 3]
maybeToList c == [1, 2, 3]
forList : (Cons a -> b) -> List a -> Maybe b
Convert a function that operates on Cons to a function that operates on List, where the empty list results in Nothing.
maximum : Cons comparable -> comparable
maximum = foldl1 max
listMaximum : List comparable -> Maybe comparable
listMaximum = forList maximum
listMaximum [] == Nothing
listMaximum [1, 2, 3] == Just 3
The following functions preserve non-emptiness, so given a cons they return a cons.
reverse : Cons a -> Cons a
Reverse the cons.
c = cons 1 [2, 3]
reverse c == cons 3 [2, 1]
append : Cons a -> Cons a -> Cons a
Append the second cons to the first.
c = cons 1 [2, 3]
d = cons 4 [5, 6]
append c d == cons 1 [2, 3, 4, 5, 6]
appendList : Cons a -> List a -> Cons a
Append a list to a cons.
c = cons 1 [2, 3]
l = [4, 5, 6]
appendList c l == cons 1 [2, 3, 4, 5, 6]
appendToList : List a -> Cons a -> Cons a
Append a cons to a list.
l = [1, 2, 3]
c = cons 4 [5, 6]
appendToList l c == cons 1 [2, 3, 4, 5, 6]
concat : Cons (Cons a) -> Cons a
Concatenate a non-empty list of non-empty lists.
c = cons 1 [2, 3]
d = singleton 4
e = cons 5 [6]
cs = cons c [d, e]
concat cs == cons 1 [2, 3, 4, 5, 6]
concat == foldr1 append
intersperse : a -> Cons a -> Cons a
Intersperse the value between each element of the cons.
c = cons "first" ["second", "third"]
intersperse "and" c == cons "first" ["and", "second", "and", "third"]
unzip : Cons ( a, b ) -> ( Cons a, Cons b )
A tuple of each cons, corresponding to a cons of tuples.
c = cons (1, "a") [(2, "b"), (3, "c")]
unzip c == (cons 1 [2, 3], cons "a" ["b", "c"])
map : (a -> b) -> Cons a -> Cons b
Apply a function to each element of the cons.
c = cons 1 [4, 9]
map sqrt c == cons 1 [2, 3]
map2 : (a -> b -> c) -> Cons a -> Cons b -> Cons c
Apply a function to each pair of elements, limited by the shortest cons.
zip : Cons a -> Cons b -> Cons (a, b)
zip = map2 (,)
c = cons 1 [2, 3]
d = cons "a" ["b", "c", "d", "e"]
zip c d = cons (1, "a") [(2, "b"), (3, "c")]
map3 : (a -> b -> c -> d) -> Cons a -> Cons b -> Cons c -> Cons d
map4 : (a -> b -> c -> d -> e) -> Cons a -> Cons b -> Cons c -> Cons d -> Cons e
map5 : (a -> b -> c -> d -> e -> f) -> Cons a -> Cons b -> Cons c -> Cons d -> Cons e -> Cons f
concatMap : (a -> Cons b) -> Cons a -> Cons b
Also known as "flat map", map each element of the cons to a cons, and then concatenate them together.
f : number -> Cons number
f x = cons x [-x]
c = cons 1 [2, 3]
concatMap f c == cons 1 [-1, 2, -2, 3, -3]
concatMap f == concat << map f
indexedMap : (Basics.Int -> a -> b) -> Cons a -> Cons b
Apply a function to each element of the cons, as well as the index.
c = cons "a" ["b", "c"]
indexedMap (,) c == cons (0, "a") [(1, "b"), (2, "c")]
scanl : (a -> b -> b) -> b -> Cons a -> Cons b
Reduce the cons from the left, producing a cons of all intermediate results.
c = cons "a" ["b", "c"]
step value result = result ++ value
scanl step "" c == cons "" ["a", "ab", "abc"]
scanlList : (a -> b -> b) -> b -> List a -> Cons b
Reduce the list from the left, producing a cons of all intermediate results, since even for the empty list there is one intermediate result.
Equivalent to List.scanl, but with a more specific return type.
step value result = result ++ value
scanlList step "" [] == cons "" []
scanlList step "" ["a", "b", "c"] == cons "" ["a", "ab", "abc"]
sort : Cons comparable -> Cons comparable
Sort the cons in ascending order.
c = cons 2 [3, 1]
sort c == cons 1 [2, 3]
sortBy : (a -> comparable) -> Cons a -> Cons a
Sort the cons in ascending order, by applying the given function to each value.
alice = {name="Alice", age=30}
bob = {name="Bob", age=20}
charlie = {name="Charlie", age=40}
c = cons alice [bob, charlie]
sortBy .age c == cons bob [alice, charlie]
sortWith : (a -> a -> Basics.Order) -> Cons a -> Cons a
Sort the cons in ascending order, based on the given comparison function.
reverseCompare : comparable -> comparable -> Order
reverseCompare x y =
case compare x y of
LT -> GT
EQ -> EQ
GT -> LT
c = cons "b" ["a", "c"]
sortWith reverseCompare c == cons "c" ["b", "a"]
sortWith compare == sort
Every function from the List library has been adapted to Cons.
The following are just convenience functions which convert the cons to a list and then apply the corresponding list function. For example, the definition of sum
is:
sum = toList >> List.sum
isEmpty : Cons a -> Basics.Bool
Always false for a cons, only here to make porting List code easier.
isEmpty == always False
length : Cons a -> Basics.Int
The number of elements in the cons.
c = cons 1 [2, 3]
length c == 3
member : a -> Cons a -> Basics.Bool
True if and only if the given element is in the given cons.
c = cons 1 [2, 3]
member 5 c == False
member 2 c == True
filter : (a -> Basics.Bool) -> Cons a -> List a
The list of elements from the cons which satisfy the given predicate. This can't generally be a cons itself, because it might be empty.
c = cons 1 [2, 3]
filter (\x -> x > 10) c == []
filter (\x -> x > 1) c == [2, 3]
take : Basics.Int -> Cons a -> List a
The first n elements of the cons, up to the length of the cons. This can't generally be a cons itself, since n might not be positive.
c = cons "a" ["b", "c"]
take 2 c == ["a", "b"]
take 100 c == toList c
take -10 c == []
drop : Basics.Int -> Cons a -> List a
The cons without its first n elements. This can't generally be a cons itself, because it might be empty.
c = cons "a" ["b", "c"]
drop 2 c == ["c"]
drop 100 c == []
drop -10 c == toList c
partition : (a -> Basics.Bool) -> Cons a -> ( List a, List a )
Partition the cons into two lists, the first containing the elements which satisfy the given predicate, the second containing the elements which don't. These can't generally be a cons themselves, since one might be empty.
c = cons 1 [2, 3]
partition (\x -> x > 1) c == ([2, 3], [1])
filterMap : (a -> Maybe b) -> Cons a -> List b
Map the given Maybe function over the cons, discarding every Nothing. This can't generally be a cons itself, because it might be empty.
String.toInt : String -> Maybe Int
c = cons "1" ["a", "2", "b"]
filterMap String.toInt c == [1, 2]
foldl : (a -> b -> b) -> b -> Cons a -> b
Reduce the cons from the left, starting with the given value. To start with the first value in the cons, use foldl1.
c = cons "a" ["b", "c"]
step value result = result ++ value
foldl1 step "x" c == "xabc"
foldr : (a -> b -> b) -> b -> Cons a -> b
Reduce the cons from the right, starting with the given value. To start with the last value in the cons, use foldr1.
c = cons "a" ["b", "c"]
step value result = result ++ value
foldr1 step "x" c == "xcba"
sum : Cons number -> number
The sum of the elements of the cons.
c = cons 2 [3, 4]
sum c == 9
product : Cons number -> number
The product of the elements of the cons.
c = cons 2 [3, 4]
product c == 24
all : (a -> Basics.Bool) -> Cons a -> Basics.Bool
True if and only if all elements of the cons satisfy the given predicate.
c = cons 1 [2, 3]
all (\x -> x > 2) == False
all (\x -> x > 0) == True
any : (a -> Basics.Bool) -> Cons a -> Basics.Bool
True if and only if any elements of the cons satisfy the given predicate.
c = cons 1 [2, 3]
any (\x -> x > 5) == False
any (\x -> x > 2) == True