├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── elm.json ├── package-lock.json ├── package.json ├── src └── Basics │ └── Extra.elm └── tests ├── Tests.elm └── elm-verify-examples.json /.gitignore: -------------------------------------------------------------------------------- 1 | # elm-package generated files 2 | elm-stuff 3 | 4 | # elm-repl generated files 5 | repl-temp-* 6 | 7 | # stoeffel/elm-verify-examples 8 | tests/VerifyExamples 9 | 10 | # Ignore backup files 11 | *~ 12 | 13 | # CI dependency directories 14 | node_modules/ 15 | 16 | # IntelliJ files 17 | .idea/ 18 | 19 | # elm-coverage reports 20 | .coverage/ 21 | 22 | # misc 23 | .DS_Store 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: "12" 3 | 4 | cache: 5 | npm: true 6 | directories: 7 | - $HOME/.elm 8 | - elm-stuff 9 | - tests/elm-stuff 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Elm Community members 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Functions to supplement Basics 2 | 3 | [![Build Status](https://travis-ci.org/elm-community/basics-extra.svg?branch=master)](https://travis-ci.org/elm-community/basics-extra) 4 | 5 | Package for functions that feel like they should be included in [Basics](https://package.elm-lang.org/packages/elm/core/latest/Basics) but for one reason or another are not. 6 | 7 | Feedback and contributions are very welcome. 8 | 9 | ## Tests 10 | 11 | This package uses [elm-test](https://github.com/elm-explorations/test) and [elm-verify-examples](https://github.com/stoeffel/elm-verify-examples). 12 | 13 | ## Contributing 14 | 15 | Pull requests are welcome. You can expect some kind of response within 1 week. 16 | 17 | If you are proposing a new function be added, please adhere to the following: 18 | 19 | 1. Include [documentation](http://package.elm-lang.org/help/documentation-format) and make sure your documentation has a code snippet demonstrating what the function does. We use [elm-verify-examples](https://github.com/stoeffel/elm-verify-examples) in our continuous integration setup to ensure the documentation examples are correct, so please take advantage of that. 20 | 2. Provide a detailed use case where your new function would be useful. Also, compare your new function to the best possible implementation that doesn't include use your function. 21 | 3. Add tests to `Tests/Tests.elm` 22 | 23 | If you are improving existing functions please demonstrate the performance gains in something like [Ellie](https://ellie-app.com/) and by using a benchmark library like [this one](http://package.elm-lang.org/packages/BrianHicks/elm-benchmark/latest). 24 | -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "elm-community/basics-extra", 4 | "summary": "Additional basic functions", 5 | "license": "MIT", 6 | "version": "4.1.0", 7 | "exposed-modules": [ 8 | "Basics.Extra" 9 | ], 10 | "elm-version": "0.19.0 <= v < 0.20.0", 11 | "dependencies": { 12 | "elm/core": "1.0.0 <= v < 2.0.0" 13 | }, 14 | "test-dependencies": { 15 | "elm-explorations/test": "2.0.1 <= v < 3.0.0", 16 | "elm-community/list-extra": "8.7.0 <= v < 9.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basics-extra", 3 | "version": "4.1.0", 4 | "scripts": { 5 | "coverage": "elm-coverage", 6 | "preview": "elm-doc-preview", 7 | "test": "elm-test && elm-verify-examples --run-tests && elm-format --validate ." 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/elm-community/basics-extra.git" 12 | }, 13 | "license": "MIT", 14 | "dependencies": { 15 | "elm": "^0.19.1-5", 16 | "elm-test": "^0.19.1-revision10" 17 | }, 18 | "devDependencies": { 19 | "elm-coverage": "^0.4.1", 20 | "elm-doc-preview": "^5.0.5", 21 | "elm-format": "^0.8.5", 22 | "elm-verify-examples": "^5.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Basics/Extra.elm: -------------------------------------------------------------------------------- 1 | module Basics.Extra exposing 2 | ( swap 3 | , maxSafeInteger, minSafeInteger, isSafeInteger 4 | , atMost, atLeast 5 | , safeDivide, safeIntegerDivide 6 | , safeModBy, safeRemainderBy, fractionalModBy 7 | , inDegrees, inRadians, inTurns 8 | , flip, curry, uncurry 9 | , orderBy, toOrder, toOrderDesc 10 | ) 11 | 12 | {-| Additional basic functions. 13 | 14 | 15 | # Tuples 16 | 17 | @docs swap 18 | 19 | 20 | # Numbers 21 | 22 | @docs maxSafeInteger, minSafeInteger, isSafeInteger 23 | 24 | 25 | # Math 26 | 27 | @docs atMost, atLeast 28 | @docs safeDivide, safeIntegerDivide 29 | @docs safeModBy, safeRemainderBy, fractionalModBy 30 | 31 | 32 | # Angles 33 | 34 | @docs inDegrees, inRadians, inTurns 35 | 36 | 37 | # Higher-Order Helpers 38 | 39 | @docs flip, curry, uncurry 40 | 41 | 42 | # Comparison & Ordering 43 | 44 | @docs orderBy, toOrder, toOrderDesc 45 | 46 | -} 47 | 48 | 49 | {-| Swaps the elements in a pair. 50 | 51 | swap ( 1, 2 ) --> ( 2, 1 ) 52 | 53 | -} 54 | swap : ( a, b ) -> ( b, a ) 55 | swap ( a, b ) = 56 | ( b, a ) 57 | 58 | 59 | {-| The maximum _safe_ value for an integer, defined as `2^53 - 1`. Anything 60 | larger than that and behaviour becomes mathematically unsound. 61 | 62 | maxSafeInteger + 1 --> maxSafeInteger + 2 63 | 64 | -} 65 | maxSafeInteger : number 66 | maxSafeInteger = 67 | 2 ^ 53 - 1 68 | 69 | 70 | {-| The minimum _safe_ value for an integer, defined as `-(2^53 - 1)`. Anything 71 | smaller than that, and behaviour becomes mathematically unsound. 72 | 73 | minSafeInteger - 1 --> minSafeInteger - 2 74 | 75 | -} 76 | minSafeInteger : number 77 | minSafeInteger = 78 | -maxSafeInteger 79 | 80 | 81 | {-| Checks if a given integer is within the safe range, meaning it is between 82 | `-(2^53 - 1)` and `2^53 - 1`. 83 | 84 | isSafeInteger 5 --> True 85 | 86 | isSafeInteger maxSafeInteger --> True 87 | 88 | isSafeInteger (maxSafeInteger + 1) --> False 89 | 90 | -} 91 | isSafeInteger : Int -> Bool 92 | isSafeInteger number = 93 | minSafeInteger <= number && maxSafeInteger >= number 94 | 95 | 96 | {-| Defines an upper bound for a variable. 97 | 98 | 42 |> atMost 0 --> 0 99 | 100 | -42 |> atMost 0 --> -42 101 | 102 | -} 103 | atMost : comparable -> comparable -> comparable 104 | atMost = 105 | min 106 | 107 | 108 | {-| Defines a lower bound for a variable. 109 | 110 | -42 |> atLeast 0 --> 0 111 | 112 | 42 |> atLeast 0 --> 42 113 | 114 | -} 115 | atLeast : comparable -> comparable -> comparable 116 | atLeast = 117 | max 118 | 119 | 120 | {-| Perform floating-point division (like Elm's `/` operator) that will never 121 | crash the app. If the `y` argument in `safeDivide x y` is zero, we return `Nothing`. 122 | 123 | safeDivide 5 2 --> Just 2.5 124 | 125 | -- the interesting part 126 | safeDivide 5 0 --> Nothing 127 | 128 | -} 129 | safeDivide : Float -> Float -> Maybe Float 130 | safeDivide x y = 131 | if y == 0 then 132 | Nothing 133 | 134 | else 135 | Just (x / y) 136 | 137 | 138 | {-| Perform integer division (like Elm's `//` operator) that will never crash 139 | the app. If the `y` argument in `safeIntegerDivide x y` is zero, we return `Nothing`. 140 | 141 | safeIntegerDivide 5 2 --> Just 2 142 | 143 | -- the interesting part 144 | safeIntegerDivide 5 0 --> Nothing 145 | 146 | -} 147 | safeIntegerDivide : Int -> Int -> Maybe Int 148 | safeIntegerDivide x y = 149 | if y == 0 then 150 | Nothing 151 | 152 | else 153 | Just (x // y) 154 | 155 | 156 | {-| Perform [modular arithmetic][ma] that will never crash the app. If the `modulus` 157 | argument in `safeModBy modulus x` is zero, we return `Nothing`. 158 | 159 | safeModBy 2 4 --> Just 0 160 | 161 | safeModBy 2 5 --> Just 1 162 | 163 | -- the interesting part 164 | safeModBy 0 4 --> Nothing 165 | 166 | Use [`safeRemainderBy`](#safeRemainderBy) for a different treatment of negative 167 | numbers, or read Daan Leijen’s [Division and Modulus for Computer Scientists][dm] 168 | for more information. 169 | 170 | [ma]: https://en.wikipedia.org/wiki/Modular_arithmetic 171 | [dm]: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/divmodnote-letter.pdf 172 | 173 | -} 174 | safeModBy : Int -> Int -> Maybe Int 175 | safeModBy modulus x = 176 | if modulus == 0 then 177 | Nothing 178 | 179 | else 180 | Just (modBy modulus x) 181 | 182 | 183 | {-| Get the remainder after division in a way that will never crash the app. If 184 | the `divisor` argument in `safeRemainderBy divisor x` is zero, we return `Nothing`. 185 | 186 | safeRemainderBy 2 4 --> Just 0 187 | 188 | safeRemainderBy 2 5 --> Just 1 189 | 190 | -- the interesting part 191 | safeRemainderBy 0 4 --> Nothing 192 | 193 | Use [`safeModBy`](#safeModBy) for a different treatment of negative 194 | numbers, or read Daan Leijen’s [Division and Modulus for Computer Scientists][dm] 195 | for more information. 196 | 197 | [dm]: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/divmodnote-letter.pdf 198 | 199 | -} 200 | safeRemainderBy : Int -> Int -> Maybe Int 201 | safeRemainderBy divisor x = 202 | if divisor == 0 then 203 | Nothing 204 | 205 | else 206 | Just (remainderBy divisor x) 207 | 208 | 209 | {-| Perform [modular arithmetic](https://en.wikipedia.org/wiki/Modular_arithmetic) 210 | involving floating point numbers. 211 | 212 | The sign of the result is the same as the sign of the `modulus` 213 | in `fractionalModBy modulus x`. 214 | 215 | fractionalModBy 2.5 5 --> 0 216 | 217 | fractionalModBy 2 4.5 == 0.5 218 | 219 | fractionalModBy 2 -4.5 == 1.5 220 | 221 | fractionalModBy -2 4.5 == -1.5 222 | 223 | -} 224 | fractionalModBy : Float -> Float -> Float 225 | fractionalModBy modulus x = 226 | x - modulus * toFloat (floor (x / modulus)) 227 | 228 | 229 | {-| Convert standard Elm angles (radians) to degrees. 230 | 231 | inDegrees (turns 2) --> 720 232 | 233 | inDegrees pi --> 180 234 | 235 | -} 236 | inDegrees : Float -> Float 237 | inDegrees angle = 238 | angle / degrees 1 239 | 240 | 241 | {-| Convert standard Elm angles (radians) to radians. 242 | 243 | inRadians (degrees 90) == pi / 2 244 | 245 | inRadians (turns 1) == 2 * pi 246 | 247 | -} 248 | inRadians : Float -> Float 249 | inRadians = 250 | identity 251 | 252 | 253 | {-| Convert standard Elm angles (radians) to turns. One turn is equal to 360°. 254 | 255 | inTurns (degrees 180) == 0.5 256 | 257 | inTurns (3 * pi) == 1.5 258 | 259 | -} 260 | inTurns : Float -> Float 261 | inTurns angle = 262 | angle / turns 1 263 | 264 | 265 | {-| Flip the order of the first two arguments to a function. 266 | -} 267 | flip : (a -> b -> c) -> (b -> a -> c) 268 | flip f b a = 269 | f a b 270 | 271 | 272 | {-| Change how arguments are passed to a function. 273 | This splits paired arguments into two separate arguments. 274 | -} 275 | curry : (( a, b ) -> c) -> a -> b -> c 276 | curry f a b = 277 | f ( a, b ) 278 | 279 | 280 | {-| Change how arguments are passed to a function. 281 | This combines two arguments into a single pair. 282 | -} 283 | uncurry : (a -> b -> c) -> ( a, b ) -> c 284 | uncurry f ( a, b ) = 285 | f a b 286 | 287 | 288 | {-| Create an ordering function that can be used to sort 289 | lists by multiple dimensions, by flattening multiple ordering functions into one. 290 | 291 | This is equivalent to `ORDER BY` in SQL. The ordering function will order 292 | its inputs based on the order that they appear in the `List (a -> a -> Order)` argument. 293 | 294 | type alias Pen = 295 | { model : String 296 | , tipWidthInMillimeters : Float 297 | } 298 | 299 | pens : List Pen 300 | pens = 301 | [ Pen "Pilot Hi-Tec-C Gel" 0.4 302 | , Pen "Morning Glory Pro Mach" 0.38 303 | , Pen "Pilot Hi-Tec-C Coleto" 0.5 304 | ] 305 | 306 | order : Pen -> Pen -> Order 307 | order = 308 | orderBy [ toOrder .tipWidthInMillimeters, toOrder .model ] 309 | 310 | List.sortWith order pens 311 | --> [ Pen "Morning Glory Pro Mach" 0.38 312 | --> , Pen "Pilot Hi-Tec-C Gel" 0.4 313 | --> , Pen "Pilot Hi-Tec-C Coleto" 0.5 314 | --> ] 315 | 316 | If our `Pen` type alias above was represented a row in a database table, our `order` function as defined above would be equivalent 317 | to this SQL clause: 318 | 319 | ORDER BY tipWidthInMillimeters, model 320 | 321 | -} 322 | orderBy : List (a -> a -> Order) -> (a -> a -> Order) 323 | orderBy comparators a b = 324 | case comparators of 325 | [] -> 326 | EQ 327 | 328 | comparator :: rest -> 329 | case comparator a b of 330 | EQ -> 331 | orderBy rest a b 332 | 333 | other -> 334 | other 335 | 336 | 337 | {-| Helper for multi-dimensional sort. 338 | 339 | Takes a function that extracts a comparable value from a type `a` as a key, 340 | and returns a function `a -> a -> Order`. 341 | 342 | This is primarily a helper function for the `orderBy` function above. 343 | 344 | {- Simple example: wrapping a function that turns 345 | a custom type into an instance of `comparable` 346 | -} 347 | 348 | type Color 349 | = Red 350 | | Yellow 351 | | Green 352 | 353 | colorToComparable : Color -> Int 354 | colorToComparable light = 355 | case light of 356 | Red -> 0 357 | Yellow -> 1 358 | Green -> 2 359 | 360 | colorToOrder : Color -> Color -> Order 361 | colorToOrder = 362 | toOrder colorToComparable 363 | 364 | List.sortWith 365 | colorToOrder 366 | [ Yellow, Yellow, Red, Green, Red ] 367 | --> [ Red, Red, Yellow, Yellow, Green ] 368 | 369 | 370 | {- More interesting example: using the property accessor 371 | methods on a custom type with `toOrder`; we only need 372 | this function when we want to combine multiple ordering functions into one. 373 | -} 374 | 375 | type alias Light = 376 | { color : Color 377 | , action : String 378 | , timeActivatedSeconds : Float 379 | } 380 | 381 | lights : List Light 382 | lights = 383 | [ Light Green "Go" 60 384 | , Light Yellow "Slow down" 5.5 385 | , Light Red "Stop" 60 386 | ] 387 | 388 | List.sortWith 389 | ( orderBy 390 | [ toOrder .timeActivatedSeconds 391 | , toOrder (.color >> colorToComparable) 392 | ] 393 | ) 394 | lights 395 | --> [ Light Yellow "Slow down" 5.5 396 | --> , Light Red "Stop" 60 397 | --> , Light Green "Go" 60 398 | --> ] 399 | 400 | (Note that `List.sortWith colorOrder` above is identical to `List.sortBy colorToComparable`.) 401 | 402 | -} 403 | toOrder : (a -> comparable) -> (a -> a -> Order) 404 | toOrder selector a b = 405 | Basics.compare (selector a) (selector b) 406 | 407 | 408 | {-| Same as `toOrder`, with flipped comparisons to enable "sort by descending". 409 | 410 | type Color 411 | = Red 412 | | Yellow 413 | | Green 414 | 415 | colorToComparable : Color -> Int 416 | colorToComparable light = 417 | case light of 418 | Red -> 0 419 | Yellow -> 1 420 | Green -> 2 421 | 422 | colorToOrder : Color -> Color -> Order 423 | colorToOrder = 424 | toOrderDesc colorToComparable 425 | 426 | List.sortWith 427 | colorToOrder 428 | [ Yellow, Yellow, Red, Green, Red ] 429 | --> [ Green, Yellow, Yellow, Red, Red ] 430 | 431 | -} 432 | toOrderDesc : (a -> comparable) -> (a -> a -> Order) 433 | toOrderDesc selector a b = 434 | Basics.compare (selector b) (selector a) 435 | -------------------------------------------------------------------------------- /tests/Tests.elm: -------------------------------------------------------------------------------- 1 | module Tests exposing (suite) 2 | 3 | import Basics.Extra 4 | exposing 5 | ( atLeast 6 | , atMost 7 | , curry 8 | , flip 9 | , fractionalModBy 10 | , inDegrees 11 | , inRadians 12 | , inTurns 13 | , isSafeInteger 14 | , maxSafeInteger 15 | , minSafeInteger 16 | , orderBy 17 | , safeDivide 18 | , safeIntegerDivide 19 | , safeModBy 20 | , safeRemainderBy 21 | , swap 22 | , toOrder 23 | , toOrderDesc 24 | , uncurry 25 | ) 26 | import Expect exposing (Expectation, FloatingPointTolerance(..)) 27 | import List.Extra as ListX 28 | import Test exposing (Test, describe, test) 29 | 30 | 31 | suite : Test 32 | suite = 33 | describe "Basics.Extra additional doc tests" 34 | [ swapTest 35 | , numbersTests 36 | , fractionalModByDocTests 37 | , inDegreesTest 38 | , inRadiansDocTests 39 | , inTurnsDocTests 40 | , higherOrderHelpersTests 41 | , orderByTests 42 | , toOrderTests 43 | , toOrderDescTests 44 | ] 45 | 46 | 47 | expectAlmostEqual : Float -> Float -> Expectation 48 | expectAlmostEqual = 49 | Expect.within (Absolute 1.0e-20) 50 | 51 | 52 | swapTest : Test 53 | swapTest = 54 | test "swap swaps" <| 55 | \() -> 56 | swap ( 2, 0 ) |> swap |> Expect.equal ( 2, 0 ) 57 | 58 | 59 | numbersTests : Test 60 | numbersTests = 61 | describe "numbers tests" 62 | [ test "maxSafeInteger behaves unexpectedly, as expected" <| 63 | \() -> 64 | maxSafeInteger + 1 |> Expect.equal (maxSafeInteger + 2) 65 | , test "minSafeInteger behaves unexpectedly, as expected" <| 66 | \() -> 67 | minSafeInteger - 1 |> Expect.equal (minSafeInteger - 2) 68 | , test "reports if an integer is within the range of safety" <| 69 | \() -> 70 | Expect.all 71 | [ \() -> 72 | isSafeInteger 5 |> Expect.equal True 73 | , \() -> 74 | isSafeInteger maxSafeInteger |> Expect.equal True 75 | , \() -> 76 | isSafeInteger minSafeInteger |> Expect.equal True 77 | , \() -> 78 | minSafeInteger - 1 |> isSafeInteger |> Expect.equal False 79 | , \() -> 80 | maxSafeInteger + 1 |> isSafeInteger |> Expect.equal False 81 | ] 82 | () 83 | , test "defines an upper bound for a variable" <| 84 | \() -> 85 | Expect.all 86 | [ \() -> 87 | 42 |> atMost 0 |> Expect.equal 0 88 | , \() -> 89 | -42 |> atMost 0 |> Expect.equal -42 90 | ] 91 | () 92 | , test "defines a lower bound for a variable" <| 93 | \() -> 94 | Expect.all 95 | [ \() -> 96 | -42 |> atLeast 0 |> Expect.equal 0 97 | , \() -> 98 | 42 |> atLeast 0 |> Expect.equal 42 99 | ] 100 | () 101 | , test "divides a floating point number safely" <| 102 | \() -> 103 | Expect.all 104 | [ \() -> 105 | safeDivide 5 2 |> Expect.equal (Just 2.5) 106 | , \() -> 107 | safeDivide 5 0 |> Expect.equal Nothing 108 | ] 109 | () 110 | , test "divides an integer safely" <| 111 | \() -> 112 | Expect.all 113 | [ \() -> 114 | safeIntegerDivide 5 2 |> Expect.equal (Just 2) 115 | , \() -> 116 | safeIntegerDivide 5 0 |> Expect.equal Nothing 117 | ] 118 | () 119 | , test "all of the modular arithmetic and none of the crashing" <| 120 | \() -> 121 | Expect.all 122 | [ \() -> 123 | safeModBy 2 4 |> Expect.equal (Just 0) 124 | , \() -> 125 | safeModBy 2 5 |> Expect.equal (Just 1) 126 | , \() -> 127 | safeModBy 0 4 |> Expect.equal Nothing 128 | ] 129 | () 130 | , test "safe remainders and none of the crashing" <| 131 | \() -> 132 | Expect.all 133 | [ \() -> 134 | safeRemainderBy 2 4 |> Expect.equal (Just 0) 135 | , \() -> 136 | safeRemainderBy 2 5 |> Expect.equal (Just 1) 137 | , \() -> 138 | safeRemainderBy 0 4 |> Expect.equal Nothing 139 | ] 140 | () 141 | ] 142 | 143 | 144 | inDegreesTest : Test 145 | inDegreesTest = 146 | test "inDegrees should behave as expected" <| 147 | \() -> 148 | inDegrees pi |> Expect.equal 180 149 | 150 | 151 | fractionalModByDocTests : Test 152 | fractionalModByDocTests = 153 | describe "fractionalModBy" 154 | [ test "example 1" <| 155 | \() -> fractionalModBy 2 4.5 |> expectAlmostEqual 0.5 156 | , test "example 2" <| 157 | \() -> fractionalModBy 2 -4.5 |> expectAlmostEqual 1.5 158 | , test "example 3" <| 159 | \() -> fractionalModBy -2 4.5 |> expectAlmostEqual -1.5 160 | ] 161 | 162 | 163 | inRadiansDocTests : Test 164 | inRadiansDocTests = 165 | describe "inRadians" 166 | [ test "example 1" <| 167 | \() -> inRadians (degrees 90) |> expectAlmostEqual (pi / 2) 168 | , test "example 2" <| 169 | \() -> inRadians (turns 1) |> expectAlmostEqual (2 * pi) 170 | ] 171 | 172 | 173 | inTurnsDocTests : Test 174 | inTurnsDocTests = 175 | describe "inTurns" 176 | [ test "example 1" <| 177 | \() -> inTurns (degrees 180) |> expectAlmostEqual 0.5 178 | , test "example 2" <| 179 | \() -> inTurns (3 * pi) |> expectAlmostEqual 1.5 180 | ] 181 | 182 | 183 | higherOrderHelpersTests : Test 184 | higherOrderHelpersTests = 185 | describe "higher-order helpers" 186 | [ test "flip" <| 187 | \() -> 188 | flip safeDivide 5 0 |> Expect.equal (Just 0) 189 | , test "curry" <| 190 | \() -> 191 | curry identity 2 4 |> Expect.equal ( 2, 4 ) 192 | , test "uncurry" <| 193 | \() -> 194 | uncurry safeDivide ( 0, 5 ) |> Expect.equal (Just 0) 195 | ] 196 | 197 | 198 | type Color 199 | = Red 200 | | Black 201 | | Blue 202 | 203 | 204 | colorToComparable : Color -> Int 205 | colorToComparable = 206 | \c -> 207 | case c of 208 | Red -> 209 | 0 210 | 211 | Black -> 212 | 1 213 | 214 | Blue -> 215 | 2 216 | 217 | 218 | type alias Car = 219 | { manufacturer : String 220 | , model : String 221 | , cylinders : Int 222 | , color : Color 223 | } 224 | 225 | 226 | fordMustangEco : Car 227 | fordMustangEco = 228 | { manufacturer = "Ford" 229 | , model = "Mustang EcoBoost" 230 | , cylinders = 4 231 | , color = Blue 232 | } 233 | 234 | 235 | fordMustangShelby : Car 236 | fordMustangShelby = 237 | { manufacturer = "Ford" 238 | , model = "Mustang Shelby GT350" 239 | , cylinders = 8 240 | , color = Red 241 | } 242 | 243 | 244 | dodgeViper : Car 245 | dodgeViper = 246 | { manufacturer = "Dodge" 247 | , model = "Viper ACR" 248 | , cylinders = 10 249 | , color = Black 250 | } 251 | 252 | 253 | bmw340i : Car 254 | bmw340i = 255 | { manufacturer = "BMW" 256 | , model = "340i" 257 | , cylinders = 6 258 | , color = Blue 259 | } 260 | 261 | 262 | carPermutations : List (List Car) 263 | carPermutations = 264 | ListX.permutations 265 | [ dodgeViper, fordMustangEco, bmw340i, fordMustangShelby ] 266 | 267 | 268 | orderByTests : Test 269 | orderByTests = 270 | let 271 | expectations : (Car -> Car -> Order) -> List (List Car -> Expectation) 272 | expectations order = 273 | List.map 274 | (\p -> 275 | List.sortWith order p |> Expect.equalLists 276 | ) 277 | carPermutations 278 | in 279 | describe "orderBy" 280 | [ test "order by manufacturer, model" <| 281 | \() -> 282 | let 283 | sorted = 284 | [ bmw340i, dodgeViper, fordMustangEco, fordMustangShelby ] 285 | 286 | order : Car -> Car -> Order 287 | order = 288 | orderBy 289 | [ toOrder .manufacturer 290 | , toOrder .model 291 | ] 292 | in 293 | Expect.all (expectations order) sorted 294 | , test "order by model, manufacturer" <| 295 | \() -> 296 | let 297 | sorted = 298 | [ bmw340i, fordMustangEco, fordMustangShelby, dodgeViper ] 299 | 300 | order : Car -> Car -> Order 301 | order = 302 | orderBy 303 | [ toOrder .model 304 | , toOrder .manufacturer 305 | ] 306 | in 307 | Expect.all (expectations order) sorted 308 | , test "order by color, cylinders, manufacturer, model" <| 309 | \() -> 310 | let 311 | sorted = 312 | [ fordMustangShelby, dodgeViper, fordMustangEco, bmw340i ] 313 | 314 | order : Car -> Car -> Order 315 | order = 316 | orderBy 317 | [ .color >> colorToComparable |> toOrder 318 | , toOrder .cylinders 319 | , toOrder .manufacturer 320 | , toOrder .model 321 | ] 322 | in 323 | Expect.all (expectations order) sorted 324 | , test "orderBy with an empty list should preserve the original ordering of the list" <| 325 | \() -> 326 | let 327 | thunkedExpectations : List (() -> Expectation) 328 | thunkedExpectations = 329 | List.map 330 | (\p -> 331 | \() -> 332 | List.sortWith (orderBy []) p 333 | |> Expect.equalLists p 334 | ) 335 | carPermutations 336 | in 337 | Expect.all thunkedExpectations () 338 | ] 339 | 340 | 341 | type alias ToOrderProduct = 342 | { field : Int } 343 | 344 | 345 | type ToOrderSum 346 | = A 347 | | B 348 | 349 | 350 | toOrderTests : Test 351 | toOrderTests = 352 | describe "toOrder" 353 | [ test "use a type alias accessor to create an ordering function for that type alias" <| 354 | \() -> 355 | let 356 | order : ToOrderProduct -> ToOrderProduct -> Order 357 | order = 358 | toOrder .field 359 | 360 | unsorted : List ToOrderProduct 361 | unsorted = 362 | [ { field = 9 } 363 | , { field = 8 } 364 | , { field = 7 } 365 | ] 366 | 367 | sorted : List ToOrderProduct 368 | sorted = 369 | [ { field = 7 } 370 | , { field = 8 } 371 | , { field = 9 } 372 | ] 373 | in 374 | List.sortWith order unsorted |> Expect.equalLists sorted 375 | , test "create a custom ordering function based on a sum type, and sort with it" <| 376 | \() -> 377 | let 378 | order : ToOrderSum -> ToOrderSum -> Order 379 | order a b = 380 | case ( a, b ) of 381 | ( A, A ) -> 382 | EQ 383 | 384 | ( B, B ) -> 385 | EQ 386 | 387 | ( A, _ ) -> 388 | LT 389 | 390 | ( B, _ ) -> 391 | GT 392 | 393 | unsorted : List ToOrderSum 394 | unsorted = 395 | [ B, A, B, A, B ] 396 | 397 | sorted : List ToOrderSum 398 | sorted = 399 | [ A, A, B, B, B ] 400 | in 401 | List.sortWith order unsorted |> Expect.equalLists sorted 402 | ] 403 | 404 | 405 | toOrderDescTests : Test 406 | toOrderDescTests = 407 | describe "toOrderDesc" 408 | [ test "verify that sorting in descending order works as expected" <| 409 | \() -> 410 | List.sortWith (toOrderDesc identity) [ 1, 2, 1, 3, 4 ] 411 | |> Expect.equalLists [ 4, 3, 2, 1, 1 ] 412 | ] 413 | -------------------------------------------------------------------------------- /tests/elm-verify-examples.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "../src", 3 | "tests": ["Basics.Extra"] 4 | } 5 | --------------------------------------------------------------------------------