├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── elm-package.json ├── elm.json ├── src └── List │ └── Nonempty.elm └── tests ├── .gitignore ├── Tests.elm └── elm-verify-examples.json /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | elm.js 3 | 4 | tests/Doc 5 | tests/VerifyExamples/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | - "12" 5 | install: 6 | - npm install -g elm@latest-0.19.1 7 | - npm install -g elm-test@elm0.19.1 8 | - npm install -g elm-verify-examples 9 | script: 10 | - elm make 11 | - elm-verify-examples 12 | - elm-test 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Max Goldstein 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Max Goldstein nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # List.Nonempty for Elm 2 | 3 | [![Build Status](https://travis-ci.org/mgold/elm-nonempty-list.svg?branch=master)](https://travis-ci.org/mgold/elm-nonempty-list) 4 | 5 | A list that is known, at compile-time, to be nonempty. This means `head` and 6 | `tail` are guaranteed to succeed and you don't have to carry Maybes throughout 7 | your program. 8 | 9 | ```elm 10 | import List.Nonempty exposing (..) 11 | 12 | one : Nonempty Int 13 | one = fromElement 2 14 | 15 | two : Nonempty Int 16 | two = cons 4 one 17 | 18 | toList two --> [4, 2] 19 | 20 | head two --> 4 21 | 22 | tail two --> [2] 23 | 24 | toList (reverse two) --> [2, 4] 25 | 26 | toList (dropTail two) --> [4] 27 | 28 | member 4 two --> True 29 | 30 | foldl1 (+) two --> 6 31 | ``` 32 | 33 | For actual usage, I recommend `import List.Nonempty as NE exposing (Nonempty)` 34 | to just import the type. 35 | 36 | ## Testing 37 | 38 | ``` 39 | npm install -g elm-test 40 | npm install -g elm-verify-examples 41 | elm verify-examples && elm test # From project root. Will require downloading packages on the first run. 42 | ``` 43 | 44 | ## Upgrading 45 | 46 | 4.0.0, for Elm 0.19, removed the following functions: `(:::)`, `scanl`, and `scanl1`. `sample` uses the new [PRNG](https://en.m.wikipedia.org/wiki/Pseudorandom_number_generator) found in `elm-lang/random`. 47 | 48 | The only breaking change from 2.x to 3.0.0 is that `andMap` has arguments 49 | reversed to allow chaining with `|>` instead of backticks. 50 | 51 | The only breaking change from 1.x to 2.0.0 is that `sample` returns a generator 52 | rather than managing seeds. 53 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3.1.0", 3 | "summary": "head and tail without the Maybe", 4 | "repository": "https://github.com/mgold/elm-nonempty-list.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "src" 8 | ], 9 | "exposed-modules": [ 10 | "List.Nonempty" 11 | ], 12 | "dependencies": { 13 | "elm-lang/core": "5.0.0 <= v < 6.0.0" 14 | }, 15 | "elm-version": "0.18.0 <= v < 0.19.0" 16 | } 17 | -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "mgold/elm-nonempty-list", 4 | "summary": "head and tail without the Maybe", 5 | "license": "BSD-3-Clause", 6 | "version": "4.2.0", 7 | "exposed-modules": [ 8 | "List.Nonempty" 9 | ], 10 | "elm-version": "0.19.0 <= v < 0.20.0", 11 | "dependencies": { 12 | "elm/core": "1.0.0 <= v < 2.0.0", 13 | "elm/random": "1.0.0 <= v < 2.0.0" 14 | }, 15 | "test-dependencies": { 16 | "elm-explorations/test": "1.0.0 <= v < 2.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/List/Nonempty.elm: -------------------------------------------------------------------------------- 1 | module List.Nonempty exposing 2 | ( Nonempty(..) 3 | , singleton, fromList 4 | , head, tail, toList, get, last, take, sample 5 | , isSingleton, length, member, all, any 6 | , cons, append, pop, reverse, concat 7 | , replaceHead, replaceTail, dropTail 8 | , map, indexedMap, map2, andMap, concatMap 9 | , filter 10 | , foldl, foldl1 11 | , zip, unzip 12 | , sort, sortBy, sortWith 13 | , dedup, uniq 14 | , fromElement 15 | ) 16 | 17 | {-| A list that cannot be empty. The head and tail can be accessed without Maybes. Most other list functions are 18 | available. 19 | 20 | 21 | # Definition 22 | 23 | @docs Nonempty 24 | 25 | 26 | # Create 27 | 28 | @docs singleton, fromList 29 | 30 | 31 | # Access 32 | 33 | @docs head, tail, toList, get, last, take, sample 34 | 35 | 36 | # Inspect 37 | 38 | Nonempty lists support equality with the usual `(==)` operator (provided their contents also support equality). 39 | 40 | @docs isSingleton, length, member, all, any 41 | 42 | 43 | # Convert 44 | 45 | @docs cons, append, pop, reverse, concat 46 | 47 | 48 | # Swap 49 | 50 | @docs replaceHead, replaceTail, dropTail 51 | 52 | 53 | # Map 54 | 55 | @docs map, indexedMap, map2, andMap, concatMap 56 | 57 | 58 | # Filter 59 | 60 | @docs filter 61 | 62 | 63 | # Fold 64 | 65 | To fold or scan from the right, reverse the list first. 66 | 67 | @docs foldl, foldl1 68 | 69 | 70 | # Zipping 71 | 72 | @docs zip, unzip 73 | 74 | 75 | # Sort 76 | 77 | @docs sort, sortBy, sortWith 78 | 79 | 80 | # Deduplicate 81 | 82 | The nonempty list's elements must support equality (e.g. not functions). Otherwise you will get a runtime error. 83 | 84 | @docs dedup, uniq 85 | 86 | 87 | # Deprecated 88 | 89 | @docs fromElement 90 | 91 | -} 92 | 93 | import Random 94 | 95 | 96 | {-| The Nonempty type. If you have both a head and tail, you can construct a 97 | nonempty list directly. Otherwise use the helpers below instead. 98 | -} 99 | type Nonempty a 100 | = Nonempty a (List a) 101 | 102 | 103 | {-| Create a singleton list with the given element. 104 | -} 105 | singleton : a -> Nonempty a 106 | singleton x = 107 | Nonempty x [] 108 | 109 | 110 | {-| Create a singleton list with the given element. Deprecated in favor of `singleton`, which is what elm-lang/core uses. 111 | -} 112 | fromElement : a -> Nonempty a 113 | fromElement = 114 | singleton 115 | 116 | 117 | {-| Create a nonempty list from an ordinary list, failing on the empty list. 118 | -} 119 | fromList : List a -> Maybe (Nonempty a) 120 | fromList ys = 121 | case ys of 122 | x :: xs -> 123 | Just (Nonempty x xs) 124 | 125 | _ -> 126 | Nothing 127 | 128 | 129 | {-| Return the head of the list. 130 | -} 131 | head : Nonempty a -> a 132 | head (Nonempty x xs) = 133 | x 134 | 135 | 136 | {-| Return the tail of the list. 137 | -} 138 | tail : Nonempty a -> List a 139 | tail (Nonempty x xs) = 140 | xs 141 | 142 | 143 | {-| Convert to an ordinary list. 144 | -} 145 | toList : Nonempty a -> List a 146 | toList (Nonempty x xs) = 147 | x :: xs 148 | 149 | 150 | {-| Get the item at the specified index. The head has index 0. Indices are modulused by the length so out-of-range 151 | errors can't happen. This means that negative indices are supported, e.g. -1 to get the last element. 152 | -} 153 | get : Int -> Nonempty a -> a 154 | get i ((Nonempty x xs) as ne) = 155 | let 156 | j = 157 | modBy (length ne) i 158 | 159 | find k ys = 160 | case ys of 161 | [] -> 162 | {- This should never happen, but to avoid Debug.crash, 163 | we return the head of the list. 164 | -} 165 | x 166 | 167 | z :: zs -> 168 | if k == 0 then 169 | z 170 | 171 | else 172 | find (k - 1) zs 173 | in 174 | if j == 0 then 175 | x 176 | 177 | else 178 | find (j - 1) xs 179 | 180 | 181 | {-| Return the last element of the list. 182 | -} 183 | last : Nonempty a -> a 184 | last (Nonempty x xs) = 185 | case xs of 186 | [] -> 187 | x 188 | 189 | [ y ] -> 190 | y 191 | 192 | _ :: rest -> 193 | last (Nonempty x rest) 194 | 195 | 196 | {-| Create a nonempty list consisting of the first n elements. If n < 1, returns a nonempty list consiting of just the first element 197 | 198 | take 2 (Nonempty 1 [ 2, 3 ]) --> Nonempty 1 [ 2 ] 199 | 200 | take 0 (Nonempty 1 [ 2, 3 ]) --> fromElement 1 201 | 202 | take -3 (Nonempty 1 [ 2, 3 ]) --> fromElement 1 203 | 204 | -} 205 | take : Int -> Nonempty a -> Nonempty a 206 | take n (Nonempty x xs) = 207 | Nonempty x (List.take (n - 1) xs) 208 | 209 | 210 | {-| Create a random generator that returns a value of the nonempty list chosen uniformly at random. 211 | -} 212 | sample : Nonempty a -> Random.Generator a 213 | sample nonempty = 214 | Random.int 0 (length nonempty - 1) 215 | |> Random.map (\i -> get i nonempty) 216 | 217 | 218 | {-| Add another element as the head of the list, pushing the previous head to the tail. 219 | -} 220 | cons : a -> Nonempty a -> Nonempty a 221 | cons y (Nonempty x xs) = 222 | Nonempty y (x :: xs) 223 | 224 | 225 | {-| Append two nonempty lists together. `(++)` is _not_ supported. 226 | -} 227 | append : Nonempty a -> Nonempty a -> Nonempty a 228 | append (Nonempty x xs) (Nonempty y ys) = 229 | Nonempty x (xs ++ y :: ys) 230 | 231 | 232 | {-| Pop and discard the head, or do nothing for a singleton list. Useful if you 233 | want to exhaust a list but hang on to the last item indefinitely. 234 | 235 | pop (Nonempty 3 [ 2, 1 ]) --> Nonempty 2 [1] 236 | 237 | pop (Nonempty 1 []) --> Nonempty 1 [] 238 | 239 | -} 240 | pop : Nonempty a -> Nonempty a 241 | pop (Nonempty x xs) = 242 | case xs of 243 | [] -> 244 | Nonempty x xs 245 | 246 | y :: ys -> 247 | Nonempty y ys 248 | 249 | 250 | {-| Reverse a nonempty list. 251 | -} 252 | reverse : Nonempty a -> Nonempty a 253 | reverse (Nonempty x xs) = 254 | let 255 | revapp : ( List a, a, List a ) -> Nonempty a 256 | revapp ( ls, c, rs ) = 257 | case rs of 258 | [] -> 259 | Nonempty c ls 260 | 261 | r :: rss -> 262 | revapp ( c :: ls, r, rss ) 263 | in 264 | revapp ( [], x, xs ) 265 | 266 | 267 | {-| Flatten a nonempty list of nonempty lists into a single nonempty list. 268 | -} 269 | concat : Nonempty (Nonempty a) -> Nonempty a 270 | concat (Nonempty xs xss) = 271 | let 272 | hd = 273 | head xs 274 | 275 | tl = 276 | tail xs ++ List.concat (List.map toList xss) 277 | in 278 | Nonempty hd tl 279 | 280 | 281 | {-| Exchange the head element while leaving the tail alone. 282 | -} 283 | replaceHead : a -> Nonempty a -> Nonempty a 284 | replaceHead y (Nonempty x xs) = 285 | Nonempty y xs 286 | 287 | 288 | {-| Exchange the tail for another while leaving the head alone. 289 | -} 290 | replaceTail : List a -> Nonempty a -> Nonempty a 291 | replaceTail ys (Nonempty x xs) = 292 | Nonempty x ys 293 | 294 | 295 | {-| Replace the tail with the empty list while leaving the head alone. 296 | -} 297 | dropTail : Nonempty a -> Nonempty a 298 | dropTail (Nonempty x xs) = 299 | Nonempty x [] 300 | 301 | 302 | {-| Map a function over a nonempty list. 303 | -} 304 | map : (a -> b) -> Nonempty a -> Nonempty b 305 | map f (Nonempty x xs) = 306 | Nonempty (f x) (List.map f xs) 307 | 308 | 309 | {-| Map a function over two nonempty lists. 310 | -} 311 | map2 : (a -> b -> c) -> Nonempty a -> Nonempty b -> Nonempty c 312 | map2 f (Nonempty x xs) (Nonempty y ys) = 313 | Nonempty (f x y) (List.map2 f xs ys) 314 | 315 | 316 | {-| Map over an arbitrary number of nonempty lists. 317 | 318 | map2 (,) xs ys == map (,) xs |> andMap ys 319 | 320 | head (map (,,) xs |> andMap ys |> andMap zs) == ( head xs, head ys, head zs ) 321 | 322 | -} 323 | andMap : Nonempty a -> Nonempty (a -> b) -> Nonempty b 324 | andMap = 325 | map2 (|>) 326 | 327 | 328 | {-| Map a given function onto a nonempty list and flatten the resulting nonempty lists. If you're chaining, you can 329 | define `andThen = flip concatMap`. 330 | -} 331 | concatMap : (a -> Nonempty b) -> Nonempty a -> Nonempty b 332 | concatMap f xs = 333 | concat (map f xs) 334 | 335 | 336 | {-| Same as `map` but the function is also applied to the index of each element (starting at zero). 337 | -} 338 | indexedMap : (Int -> a -> b) -> Nonempty a -> Nonempty b 339 | indexedMap f (Nonempty x xs) = 340 | let 341 | wrapped i d = 342 | f (i + 1) d 343 | in 344 | Nonempty (f 0 x) (List.indexedMap wrapped xs) 345 | 346 | 347 | {-| Determine if the nonempty list has exactly one element. 348 | -} 349 | isSingleton : Nonempty a -> Bool 350 | isSingleton (Nonempty x xs) = 351 | List.isEmpty xs 352 | 353 | 354 | {-| Find the length of the nonempty list. 355 | -} 356 | length : Nonempty a -> Int 357 | length (Nonempty x xs) = 358 | List.length xs + 1 359 | 360 | 361 | {-| Determine if an element is present in the nonempty list. 362 | -} 363 | member : a -> Nonempty a -> Bool 364 | member y (Nonempty x xs) = 365 | x == y || List.member y xs 366 | 367 | 368 | {-| Determine if all elements satisfy the predicate. 369 | -} 370 | all : (a -> Bool) -> Nonempty a -> Bool 371 | all f (Nonempty x xs) = 372 | f x && List.all f xs 373 | 374 | 375 | {-| Determine if any elements satisfy the predicate. 376 | -} 377 | any : (a -> Bool) -> Nonempty a -> Bool 378 | any f (Nonempty x xs) = 379 | f x || List.any f xs 380 | 381 | 382 | {-| Filter a nonempty list. If all values are filtered out, return the singleton list containing the default value 383 | provided. If any value is retained, the default value is not used. If you want to deal with a Maybe instead, use 384 | `toList >> List.filter yourPredicate >> fromList`. 385 | 386 | isEven : Int -> Bool 387 | isEven n = modBy 2 n == 0 388 | 389 | filter isEven 0 (Nonempty 7 [2, 5]) --> fromElement 2 390 | 391 | filter isEven 0 (Nonempty 7 []) --> fromElement 0 392 | 393 | -} 394 | filter : (a -> Bool) -> a -> Nonempty a -> Nonempty a 395 | filter p d (Nonempty x xs) = 396 | if p x then 397 | Nonempty x (List.filter p xs) 398 | 399 | else 400 | case List.filter p xs of 401 | [] -> 402 | Nonempty d [] 403 | 404 | y :: ys -> 405 | Nonempty y ys 406 | 407 | 408 | insertWith : (a -> a -> Order) -> a -> List a -> Nonempty a 409 | insertWith cmp hd aList = 410 | case aList of 411 | x :: xs -> 412 | if cmp x hd == LT then 413 | Nonempty x (List.sortWith cmp (hd :: xs)) 414 | 415 | else 416 | Nonempty hd aList 417 | 418 | [] -> 419 | Nonempty hd [] 420 | 421 | 422 | {-| Sort a nonempty list of comparable things, lowest to highest. 423 | -} 424 | sort : Nonempty comparable -> Nonempty comparable 425 | sort (Nonempty x xs) = 426 | insertWith compare x (List.sort xs) 427 | 428 | 429 | {-| Sort a nonempty list of things by a derived property. 430 | -} 431 | sortBy : (a -> comparable) -> Nonempty a -> Nonempty a 432 | sortBy f (Nonempty x xs) = 433 | insertWith (\a b -> compare (f a) (f b)) x (List.sortBy f xs) 434 | 435 | 436 | {-| Sort a nonempty list of things by a custom comparison function. 437 | -} 438 | sortWith : (a -> a -> Order) -> Nonempty a -> Nonempty a 439 | sortWith f (Nonempty x xs) = 440 | insertWith f x <| List.sortWith f xs 441 | 442 | 443 | {-| Remove _adjacent_ duplicate elements from the nonempty list. 444 | 445 | dedup (Nonempty 1 [ 2, 2, 1 ]) --> Nonempty 1 [2, 1] 446 | 447 | -} 448 | dedup : Nonempty a -> Nonempty a 449 | dedup (Nonempty x xs) = 450 | let 451 | dedupe : a -> List a -> List a -> Nonempty a 452 | dedupe prev done next = 453 | case next of 454 | [] -> 455 | Nonempty prev done 456 | 457 | y :: ys -> 458 | if y == prev then 459 | dedupe prev done ys 460 | 461 | else 462 | dedupe y (prev :: done) ys 463 | in 464 | reverse <| dedupe x [] xs 465 | 466 | 467 | {-| Remove _all_ duplicate elements from the nonempty list, with the remaining elements ordered by first occurrence. 468 | 469 | uniq (Nonempty 1 [ 2, 2, 1 ]) --> Nonempty 1 [2] 470 | 471 | -} 472 | uniq : Nonempty a -> Nonempty a 473 | uniq (Nonempty x xs) = 474 | let 475 | unique : List a -> Nonempty a -> List a -> Nonempty a 476 | unique seen done next = 477 | case next of 478 | [] -> 479 | done 480 | 481 | y :: ys -> 482 | if List.member y seen then 483 | unique seen done ys 484 | 485 | else 486 | unique (y :: seen) (cons y done) ys 487 | in 488 | reverse <| unique [ x ] (Nonempty x []) xs 489 | 490 | 491 | {-| Reduce a nonempty list from the left with a base case. 492 | 493 | foldl (++) "" (Nonempty "a" [ "b", "c" ]) --> "cba" 494 | 495 | -} 496 | foldl : (a -> b -> b) -> b -> Nonempty a -> b 497 | foldl f b (Nonempty x xs) = 498 | List.foldl f b (x :: xs) 499 | 500 | 501 | {-| Reduce a nonempty list from the left _without_ a base case. As per Elm convention, the first argument is the current 502 | element and the second argument is the accumulated value. The function is first invoked on the _second_ element, using 503 | the first element as the accumulated value, except for singleton lists in which has the head is returned. 504 | 505 | foldl1 (++) (Nonempty "a" ["b", "c"]) --> "cba" 506 | 507 | foldl1 (++) (fromElement "a") --> "a" 508 | 509 | findMe : Int 510 | findMe = 42 511 | 512 | minimizeMe : Int -> Int 513 | minimizeMe n = abs (n-findMe) 514 | 515 | nearest : Int 516 | nearest = foldl1 (\a b -> if minimizeMe a < minimizeMe b then a else b) (Nonempty 10 [20,30,40,50,60]) 517 | 518 | nearest --> 40 519 | 520 | -} 521 | foldl1 : (a -> a -> a) -> Nonempty a -> a 522 | foldl1 f (Nonempty x xs) = 523 | List.foldl f x xs 524 | 525 | 526 | {-| Take two nonempty lists and compose them into a nonempty list of corresponding pairs. 527 | 528 | The length of the new list equals the length of the smallest list given. 529 | 530 | -} 531 | zip : Nonempty a -> Nonempty b -> Nonempty ( a, b ) 532 | zip (Nonempty x xs) (Nonempty y ys) = 533 | Nonempty ( x, y ) (List.map2 Tuple.pair xs ys) 534 | 535 | 536 | {-| Decompose a nonempty list of tuples into a tuple of nonempty lists. 537 | 538 | batters : Nonempty (String, Int) 539 | batters = Nonempty ("Barry Bonds", 762) [("Hank Aaron", 755), ("Babe Ruth", 714)] 540 | 541 | unzip batters --> (Nonempty "Barry Bonds" ["Hank Aaron", "Babe Ruth"], Nonempty 762 [755, 714]) 542 | 543 | -} 544 | unzip : Nonempty ( a, b ) -> ( Nonempty a, Nonempty b ) 545 | unzip (Nonempty ( x, y ) rest) = 546 | let 547 | ( xs, ys ) = 548 | List.unzip rest 549 | in 550 | ( Nonempty x xs, Nonempty y ys ) 551 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | /elm-stuff/ 2 | -------------------------------------------------------------------------------- /tests/Tests.elm: -------------------------------------------------------------------------------- 1 | module Tests exposing (dedupeSuite, f, getSuite, isEven, lastSuite, nonemptylist, sizeSuite, testSuite, uncurry, uniqSuite) 2 | 3 | import Expect 4 | import Fuzz exposing (char, int, list, string, tuple, tuple3) 5 | import List.Nonempty as NE 6 | import Random 7 | import String 8 | import Task exposing (Task) 9 | import Test exposing (Test, describe, fuzz, fuzz2, fuzz3, test) 10 | 11 | 12 | nonemptylist elem = 13 | tuple ( elem, list elem ) 14 | 15 | 16 | isEven n = 17 | modBy 2 n == 0 18 | 19 | 20 | f x = 21 | x * 2 22 | 23 | 24 | uncurry g ( x, y ) = 25 | g x y 26 | 27 | 28 | testSuite = 29 | describe "fuzz tests" 30 | [ fuzz (nonemptylist int) "dropping tail makes singleton" <| 31 | \( x, xs ) -> 32 | NE.Nonempty x xs |> NE.dropTail |> NE.isSingleton |> Expect.true "dropped tail not a singleton" 33 | , fuzz (nonemptylist int) "converting to and from a normal list is the identity" <| 34 | \( x, xs ) -> 35 | NE.Nonempty x xs |> NE.toList |> NE.fromList |> Expect.equal (Just (NE.Nonempty x xs)) 36 | , fuzz (nonemptylist int) "length is 1 more than `length tail`" <| 37 | \( x, xs ) -> NE.Nonempty x xs |> NE.length |> Expect.equal (List.length xs + 1) 38 | , fuzz2 int (nonemptylist int) "cons works" <| 39 | \y ( x, xs ) -> 40 | NE.cons y (NE.Nonempty x xs) |> NE.toList |> Expect.equal (y :: x :: xs) 41 | , fuzz int "fromElement results in a singleton" <| 42 | \x -> NE.fromElement x |> NE.isSingleton |> Expect.true "fromElement x not a singleton" 43 | , fuzz (tuple ( nonemptylist int, nonemptylist int )) "append works" <| 44 | \( ( x, xs ), ( y, ys ) ) -> 45 | NE.append (NE.Nonempty x xs) (NE.Nonempty y ys) |> NE.toList |> Expect.equal (x :: xs ++ y :: ys) 46 | , fuzz (tuple ( nonemptylist int, nonemptylist int )) "append never results in a singleton" <| 47 | \( ( x, xs ), ( y, ys ) ) -> 48 | NE.append (NE.Nonempty x xs) (NE.Nonempty y ys) |> NE.isSingleton |> Expect.false "got a singleton" 49 | , fuzz (nonemptylist int) "get 0 == head" <| 50 | \( x, xs ) -> 51 | NE.Nonempty x xs |> NE.get 0 |> Expect.equal x 52 | , fuzz2 int int "getting any index from singleton produces the value" <| 53 | \x i -> NE.fromElement x |> NE.get i |> Expect.equal x 54 | , fuzz int "sample will eventually produce every element" <| 55 | \i -> 56 | let 57 | gen = 58 | NE.sample (NE.Nonempty 1 [ 2, 3, 4, 5, 6 ]) |> Random.list 80 59 | in 60 | Random.step gen (Random.initialSeed i) 61 | |> Tuple.first 62 | |> NE.fromList 63 | |> Maybe.map NE.uniq 64 | |> Maybe.map (\ne -> NE.length ne == 6) 65 | |> Expect.equal (Just True) 66 | , fuzz (list int) "fromList fails only for the empty List" <| 67 | \xs -> 68 | case NE.fromList xs of 69 | Just _ -> 70 | List.isEmpty xs |> Expect.false "fromList made Just x from an empty list" 71 | 72 | Nothing -> 73 | List.isEmpty xs |> Expect.true "fromList made Nothing from a nonempty list" 74 | , fuzz (nonemptylist int) "map then toList == List.map" <| 75 | \( x, xs ) -> 76 | NE.Nonempty x xs 77 | |> NE.map f 78 | |> NE.toList 79 | |> Expect.equal (List.map f (x :: xs)) 80 | , fuzz (tuple ( nonemptylist int, nonemptylist string )) "length (map2 Tuple.pair xs ys) == min (length xs) (length ys)" <| 81 | \( ( x, xs ), ( y, ys ) ) -> 82 | NE.length (NE.map2 Tuple.pair (NE.Nonempty x xs) (NE.Nonempty y ys)) 83 | |> Expect.equal (1 + min (List.length xs) (List.length ys)) 84 | , fuzz (tuple ( nonemptylist int, nonemptylist string )) 85 | "map2 Tuple.pair xs ys == map Tuple.pair xs |> andMap ys" 86 | <| 87 | \( ( x, xs ), ( y, ys ) ) -> 88 | let 89 | expected = 90 | NE.map2 Tuple.pair (NE.Nonempty x xs) (NE.Nonempty y ys) 91 | 92 | actual = 93 | NE.map Tuple.pair (NE.Nonempty x xs) |> NE.andMap (NE.Nonempty y ys) 94 | in 95 | Expect.equal expected actual 96 | , fuzz3 (nonemptylist int) (nonemptylist string) (nonemptylist char) "head (map (,,) xs |> andMap ys |> andMap zs) == (head xs, head ys, head zs)" <| 97 | \( x, xs ) ( y, ys ) ( z, zs ) -> 98 | NE.map (\a b c -> ( a, b, c )) (NE.Nonempty x xs) 99 | |> NE.andMap (NE.Nonempty y ys) 100 | |> NE.andMap (NE.Nonempty z zs) 101 | |> NE.head 102 | |> Expect.equal ( x, y, z ) 103 | , fuzz (nonemptylist int) "concatMap works the same as for a list" <| 104 | \( x, xs ) -> 105 | NE.concatMap (\y -> NE.Nonempty y [ f y ]) (NE.Nonempty x xs) 106 | |> NE.toList 107 | |> Expect.equal (List.concatMap (\y -> [ y, f y ]) (x :: xs)) 108 | , fuzz (nonemptylist int) "indexedMap works the same as for a list" <| 109 | \( x, xs ) -> 110 | NE.indexedMap Tuple.pair (NE.Nonempty x xs) 111 | |> NE.toList 112 | |> Expect.equal (List.indexedMap Tuple.pair (x :: xs)) 113 | , fuzz (nonemptylist int) "filter works" <| 114 | \( x, xs ) -> 115 | NE.Nonempty x xs 116 | |> NE.filter isEven -99 117 | |> NE.toList 118 | |> Expect.equal 119 | (let 120 | filtered = 121 | List.filter isEven (x :: xs) 122 | in 123 | if List.isEmpty filtered then 124 | [ -99 ] 125 | 126 | else 127 | filtered 128 | ) 129 | , fuzz2 (nonemptylist int) int "Filtering everything out results in the default value" <| 130 | \( x, xs ) d -> NE.Nonempty x xs |> NE.filter (always False) d |> NE.toList |> Expect.equal [ d ] 131 | , fuzz2 (nonemptylist int) int "Filtering nothing out is the identity" <| 132 | \( x, xs ) d -> NE.Nonempty x xs |> NE.filter (always True) d |> Expect.equal (NE.Nonempty x xs) 133 | , fuzz (nonemptylist int) "Equal lists equate true" <| 134 | \( x, xs ) -> NE.Nonempty x xs |> Expect.equal (NE.map identity (NE.Nonempty x xs)) 135 | , fuzz2 (nonemptylist int) int "Lists of nonequal length equate false" <| 136 | \( x, xs ) d -> 137 | NE.Nonempty d (x :: xs) 138 | |> Expect.notEqual (NE.Nonempty x xs) 139 | , fuzz (nonemptylist int) "Lists with unequal heads equate false" <| 140 | \( x, xs ) -> NE.Nonempty x xs == NE.Nonempty (x + 1) xs |> Expect.false "lists were equal" 141 | , fuzz (nonemptylist int) "popping reduces the length by 1 except for singleton lists" <| 142 | \( x, xs ) -> 143 | let 144 | ys = 145 | NE.Nonempty x xs 146 | 147 | lengthReduced = 148 | NE.length ys - 1 == NE.length (NE.pop ys) 149 | in 150 | Expect.true "popping not working correctly" <| xor lengthReduced (NE.isSingleton ys) 151 | , fuzz (nonemptylist int) "pop xs == tail xs except for singleton lists" <| 152 | \( x, xs ) -> 153 | let 154 | ys = 155 | NE.Nonempty x xs 156 | 157 | tailEquals = 158 | NE.toList (NE.pop ys) == xs 159 | in 160 | Expect.true "popping not working correctly" <| tailEquals || NE.isSingleton ys 161 | , fuzz (nonemptylist int) "reversing twice is the identity" <| 162 | \( x, xs ) -> 163 | let 164 | ys = 165 | NE.Nonempty x xs 166 | in 167 | NE.reverse (NE.reverse ys) |> Expect.equal ys 168 | , fuzz (nonemptylist int) "reversing is equal to the ordinary list reverse" <| 169 | \( x, xs ) -> 170 | NE.Nonempty x xs 171 | |> NE.reverse 172 | |> NE.toList 173 | |> Expect.equal (List.reverse (x :: xs)) 174 | , fuzz3 (list int) int (list int) "replaceTail is equal to doing so with an ordinary list" <| 175 | \ys x xs -> 176 | NE.Nonempty x xs 177 | |> NE.replaceTail ys 178 | |> NE.toList 179 | |> Expect.equal (x :: ys) 180 | , fuzz (nonemptylist (nonemptylist int)) 181 | "concat is equal to doing so with an ordinary list" 182 | <| 183 | \( ( x, xs ), ys ) -> 184 | let 185 | zs : NE.Nonempty (NE.Nonempty Int) 186 | zs = 187 | NE.Nonempty (NE.Nonempty x xs) (List.map (uncurry NE.Nonempty) ys) 188 | 189 | ys_ = 190 | List.map (uncurry (::)) ys 191 | 192 | expected = 193 | List.concat ((x :: xs) :: ys_) 194 | in 195 | NE.concat zs |> NE.toList |> Expect.equal expected 196 | , fuzz3 int (list int) int "member checks the head and the tail" <| 197 | \x xs y -> 198 | let 199 | expected = 200 | x == y || List.member y xs 201 | in 202 | NE.Nonempty x xs |> NE.member y |> Expect.equal expected 203 | , fuzz (nonemptylist string) "foldl is the same as for a list" <| 204 | \( x, xs ) -> 205 | NE.Nonempty x xs 206 | |> NE.foldl (++) "" 207 | |> Expect.equal (List.foldl (++) "" (x :: xs)) 208 | , fuzz (nonemptylist string) "foldl1 is the same as for a list" <| 209 | \( x, xs ) -> 210 | NE.Nonempty x xs 211 | |> NE.foldl1 (++) 212 | |> Expect.equal (List.foldl (++) "" (x :: xs)) 213 | , fuzz (nonemptylist string) "sort is the same as for a list" <| 214 | \( x, xs ) -> 215 | NE.Nonempty x xs 216 | |> NE.sort 217 | |> NE.toList 218 | |> Expect.equal (List.sort (x :: xs)) 219 | , fuzz (nonemptylist string) "sortBy is the same as for a list" <| 220 | \( x, xs ) -> 221 | let 222 | expected = 223 | List.map (\s -> { name = s }) (x :: xs) |> List.sortBy .name 224 | in 225 | NE.Nonempty x xs 226 | |> NE.map (\s -> { name = s }) 227 | |> NE.sortBy .name 228 | |> NE.toList 229 | |> Expect.equal expected 230 | , fuzz (nonemptylist string) "sortWith is the same as for a list" <| 231 | \( x, xs ) -> 232 | NE.Nonempty x xs 233 | |> NE.sortWith compare 234 | |> NE.toList 235 | |> Expect.equal (List.sortWith compare (x :: xs)) 236 | , fuzz2 int (nonemptylist string) "take is same as for list if result is nonempty, else just first element" <| 237 | \n ( x, xs ) -> 238 | let 239 | listResult = 240 | List.take n (x :: xs) 241 | 242 | expectedResult = 243 | case listResult of 244 | [] -> 245 | [ x ] 246 | 247 | y -> 248 | y 249 | in 250 | NE.take n (NE.Nonempty x xs) 251 | |> NE.toList 252 | |> Expect.equalLists expectedResult 253 | ] 254 | 255 | 256 | dedupeSuite = 257 | let 258 | mk x xs = 259 | NE.Nonempty x xs |> NE.dedup |> NE.toList 260 | in 261 | describe "deduplication" 262 | [ test "singleton" <| 263 | \_ -> mk 1 [] |> Expect.equal [ 1 ] 264 | , test "two different elements" <| 265 | \_ -> mk 1 [ 2 ] |> Expect.equal [ 1, 2 ] 266 | , test "repeated elements on end" <| 267 | \_ -> mk 1 [ 2, 2 ] |> Expect.equal [ 1, 2 ] 268 | , test "repeated elements at from" <| 269 | \_ -> mk 1 [ 1, 2 ] |> Expect.equal [ 1, 2 ] 270 | , test "repeated elements at front and in middle" <| 271 | \_ -> mk 1 [ 1, 2, 2, 1 ] |> Expect.equal [ 1, 2, 1 ] 272 | , test "many repeated inner elements" <| 273 | \_ -> mk 1 [ 1, 2, 2, 2, 2, 2, 1 ] |> Expect.equal [ 1, 2, 1 ] 274 | , test "some inner repeats, some not" <| 275 | \_ -> mk 1 [ 1, 2, 2, 3, 4, 4, 5 ] |> Expect.equal [ 1, 2, 3, 4, 5 ] 276 | , test "some inner repeats, some not, with repeat on the end" <| 277 | \_ -> mk 1 [ 1, 2, 2, 3, 2, 2, 1, 1 ] |> Expect.equal [ 1, 2, 3, 2, 1 ] 278 | , test "a range is already deduplicated" <| 279 | \_ -> mk 1 (List.range 1 4) |> Expect.equal (List.range 1 4) 280 | , test "first and last elements are the same doesn't change" <| 281 | \_ -> mk 3 (List.range 1 3) |> Expect.equal [ 3, 1, 2, 3 ] 282 | ] 283 | 284 | 285 | uniqSuite = 286 | let 287 | mk x xs = 288 | NE.Nonempty x xs |> NE.uniq |> NE.toList 289 | in 290 | describe "uniq" 291 | [ test "singleton" <| 292 | \_ -> mk 1 [] |> Expect.equal [ 1 ] 293 | , test "two different elements" <| 294 | \_ -> mk 1 [ 2 ] |> Expect.equal [ 1, 2 ] 295 | , test "repeated elements on end" <| 296 | \_ -> mk 1 [ 2, 2 ] |> Expect.equal [ 1, 2 ] 297 | , test "repeated elements at from" <| 298 | \_ -> mk 1 [ 1, 2 ] |> Expect.equal [ 1, 2 ] 299 | , test "repeated elements at front and in middle" <| 300 | \_ -> mk 1 [ 1, 2, 2, 1 ] |> Expect.equal [ 1, 2 ] 301 | , test "many repeated inner elements" <| 302 | \_ -> mk 1 [ 1, 2, 2, 2, 2, 2, 1 ] |> Expect.equal [ 1, 2 ] 303 | , test "some inner repeats, some not" <| 304 | \_ -> mk 1 [ 1, 2, 2, 3, 4, 4, 5 ] |> Expect.equal [ 1, 2, 3, 4, 5 ] 305 | , test "some inner repeats, some not, with repeat on the end" <| 306 | \_ -> mk 1 [ 1, 2, 2, 3, 2, 2, 1, 1 ] |> Expect.equal [ 1, 2, 3 ] 307 | , test "a range is already deduplicated" <| 308 | \_ -> mk 1 (List.range 1 4) |> Expect.equal (List.range 1 4) 309 | , test "first and last elements are the same" <| 310 | \_ -> mk 3 (List.range 1 3) |> Expect.equal [ 3, 1, 2 ] 311 | ] 312 | 313 | 314 | getSuite = 315 | let 316 | xs = 317 | NE.Nonempty 10 [ 11, 12 ] 318 | in 319 | describe "get" 320 | [ test "-4" <| \_ -> NE.get -4 xs |> Expect.equal 12 321 | , test "-3" <| \_ -> NE.get -3 xs |> Expect.equal 10 322 | , test "-2" <| \_ -> NE.get -2 xs |> Expect.equal 11 323 | , test "-1" <| \_ -> NE.get -1 xs |> Expect.equal 12 324 | , test "0" <| \_ -> NE.get 0 xs |> Expect.equal 10 325 | , test "1" <| \_ -> NE.get 1 xs |> Expect.equal 11 326 | , test "2" <| \_ -> NE.get 2 xs |> Expect.equal 12 327 | , test "3" <| \_ -> NE.get 3 xs |> Expect.equal 10 328 | ] 329 | 330 | 331 | lastSuite = 332 | describe "last" 333 | [ test "one element" <| \_ -> NE.Nonempty 12 [] |> NE.last |> Expect.equal 12 334 | , test "two elements" <| \_ -> NE.Nonempty 12 [ 13 ] |> NE.last |> Expect.equal 13 335 | , test "many elements" <| \_ -> NE.Nonempty 12 [ 13, 14, 14, 15, 16, 17 ] |> NE.last |> Expect.equal 17 336 | ] 337 | 338 | 339 | sizeSuite = 340 | describe "test large lists" 341 | [ test "500,001 items" <| 342 | \_ -> 343 | let 344 | testNumberData = 345 | NE.Nonempty 1 (List.repeat 50000 [ -1, 2, -1, 2, 4, 5, 6, -1, 2, -1 ] |> List.concat) 346 | in 347 | Expect.equal (NE.length testNumberData) (NE.length (NE.sort testNumberData)) 348 | ] 349 | -------------------------------------------------------------------------------- /tests/elm-verify-examples.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "../src", 3 | "tests": [ 4 | "List.Nonempty", 5 | "./README.md" 6 | ] 7 | } 8 | --------------------------------------------------------------------------------