├── README.md ├── concat-benchmark.hs └── hlint.yaml /README.md: -------------------------------------------------------------------------------- 1 | # Haskell's Dangerous Functions 2 | 3 | ## What does dangerous mean? 4 | 5 | Dangerous could mean either of these: 6 | 7 | - Partial: can throw exceptions in pure code 8 | - Unsafe: can cause segfaults 9 | - Has unexpected performance characteristics 10 | - Doesn't do what you want 11 | - Doesn't do what you think it does 12 | 13 | ## How to forbid these dangerous functions in your codebase 14 | 15 | 1. Copy the `hlint.yaml` file in this repository to `.hlint.yaml` within your repository 16 | 17 | ``` 18 | cat /path/to/haskell-dangerous-functions >> /path/to/your/project/.hlint.yaml 19 | ``` 20 | 21 | 2. Run `hlint` on your code. 22 | Make sure to require new changes to be `hlint`-clean. 23 | You can use `hlint --default` to generate a settings file ignoring all the hints currently outstanding. 24 | You can use [pre-commit hooks](https://pre-commit.com/) to forbid committing non-`hlint`-clean changes. 25 | 26 | 3. Whenever you want to make an exception, and use a forbidden function anyway, use the `ignore` key to add an exception to the `.hlint.yaml` file. 27 | 28 | 29 | ## FAQ 30 | 31 | 32 | * **It seems pretty silly that these functions still exist, and their dangers not well-documented.** 33 | 34 | I know! See [the relevant discussion on the GHC issue tracker](https://gitlab.haskell.org/ghc/ghc/-/merge_requests/5206). 35 | 36 | * **Surely everyone knows about these?** 37 | 38 | Maybe, but I certainly didn't, until they caused real production issues. 39 | 40 | ## Contributing 41 | 42 | **WANTED: Evidence of the danger in these functions.** 43 | If you can showcase a public incident with real-world consequences that happened because of one of these functions, I would love to refer to it in this document! 44 | 45 | 46 | If you know about another dangerous function that should be avoided, feel free to submit a PR! 47 | Please include: 48 | 49 | * an `hlint` config to forbid the function in [`hlint.yaml`](hlint.yaml). 50 | * a section in this document with: 51 | * Why the function is dangerous 52 | * A reproducible way of showing that it is dangerous. 53 | * An alternative to the dangerous function 54 | 55 | It might be that the function you have in mind is not dangerous but still weird. 56 | In that case you can add it to [the Haskell WAT list](https://github.com/NorfairKing/haskell-WAT). 57 | 58 | 59 | 60 | ## Overview of the dangerous functions 61 | 62 | 63 | ### [`forkIO`](https://hackage.haskell.org/package/base-4.14.1.0/docs/Control-Concurrent.html#v:forkIO) 64 | 65 | TL;DR: Using `forkIO` is _VERY_ hard to get right, use the async library instead. 66 | 67 | The main issue is that when threads spawned using `forkIO` throw an exception, this exception is not rethrown in the thread that spawned that thread. 68 | 69 | As an example, suppose we `forkIO` a server and something goes wrong. 70 | The main thread will not notice that anything went wrong. 71 | The only indication that an exception was thrown will be that something is printed on `stderr`. 72 | 73 | ``` haskell 74 | $ cat test.hs 75 | #!/usr/bin/env stack 76 | -- stack --resolver lts-15.15 script 77 | {-# LANGUAGE NumericUnderscores #-} 78 | import Control.Concurrent 79 | main :: IO () 80 | main = do 81 | putStrLn "Starting our 'server'." 82 | forkIO $ do 83 | putStrLn "Serving..." 84 | threadDelay 1_000_000 85 | putStrLn "Oh no, about to crash!" 86 | threadDelay 1_000_000 87 | putStrLn "Aaaargh" 88 | undefined 89 | threadDelay 5_000_000 90 | putStrLn "Still running, eventhough we crashed" 91 | threadDelay 5_000_000 92 | putStrLn "Ok that's enough of that, stopping here." 93 | ``` 94 | 95 | Which outputs: 96 | 97 | ``` 98 | $ ./test.hs 99 | Starting our 'server'. 100 | Serving... 101 | Oh no, about to crash! 102 | Aaaargh 103 | test.hs: Prelude.undefined 104 | CallStack (from HasCallStack): 105 | error, called at libraries/base/GHC/Err.hs:80:14 in base:GHC.Err 106 | undefined, called at /home/syd/test/test.hs:17:5 in main:Main 107 | Still running, eventhough we crashed 108 | Ok that's enough of that, stopping here. 109 | ``` 110 | 111 | Instead, we can use [`concurrently_` from the `async` package](http://hackage.haskell.org/package/async-2.2.3/docs/Control-Concurrent-Async.html#v:concurrently_): 112 | 113 | ``` haskell 114 | $ cat test.hs 115 | -- stack --resolver lts-15.15 script 116 | 117 | {-# LANGUAGE NumericUnderscores #-} 118 | 119 | import Control.Concurrent 120 | import Control.Concurrent.Async 121 | 122 | main :: IO () 123 | main = do 124 | putStrLn "Starting our 'server'." 125 | let runServer = do 126 | putStrLn "Serving..." 127 | threadDelay 1_000_000 128 | putStrLn "Oh no, about to crash!" 129 | threadDelay 1_000_000 130 | putStrLn "Aaaargh" 131 | undefined 132 | let mainThread = do 133 | threadDelay 5_000_000 134 | putStrLn "Still running, eventhough we crashed" 135 | threadDelay 5_000_000 136 | putStrLn "Ok that's enough of that, stopping here." 137 | concurrently_ runServer mainThread 138 | ``` 139 | 140 | to output: 141 | 142 | ``` 143 | $ ./test.hs 144 | Starting our 'server'. 145 | Serving... 146 | Oh no, about to crash! 147 | Aaaargh 148 | test.hs: Prelude.undefined 149 | CallStack (from HasCallStack): 150 | error, called at libraries/base/GHC/Err.hs:80:14 in base:GHC.Err 151 | undefined, called at /home/syd/test.hs:18:9 in main:Main 152 | ``` 153 | 154 | See also: 155 | 156 | * https://github.com/informatikr/hedis/issues/165 157 | * https://github.com/hreinhardt/amqp/issues/96 158 | 159 | ### [`forkProcess`](https://hackage.haskell.org/package/unix-2.7.2.2/docs/System-Posix-Process.html#v:forkProcess) 160 | 161 | Mostly impossible to get right. 162 | You probably want to be using [the `async` library](http://hackage.haskell.org/package/async) instead. 163 | 164 | If you think "I know what I'm doing" then you're probably still wrong. 165 | Rethink what you're doing entirely. 166 | 167 | See also https://www.reddit.com/r/haskell/comments/jsap9r/how_dangerous_is_forkprocess/ 168 | 169 | ### Partial functions 170 | 171 | #### [`head`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-List.html#v:head) 172 | 173 | Throws an exception in pure code when the input is an empty list. 174 | 175 | ``` 176 | Prelude> head [] 177 | *** Exception: Prelude.head: empty list 178 | ``` 179 | 180 | Use `listToMaybe` instead. 181 | 182 | Applies to [Data.Text.head](https://hackage.haskell.org/package/text-1.2.3.2/docs/Data-Text.html#v:head) as well 183 | 184 | Trail of destruction: 185 | 186 | * [`Prelude.head: empty list` makes hls unusable until restart](https://github.com/haskell/haskell-language-server/issues/1618) 187 | 188 | #### [`tail`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-List.html#v:tail) 189 | 190 | Throws an exception in pure code when the input is an empty list. 191 | 192 | ``` 193 | Prelude> tail [] 194 | *** Exception: Prelude.tail: empty list 195 | ``` 196 | 197 | Use `drop 1` or a case-match instead. 198 | 199 | Applies to [Data.Text.tail](https://hackage.haskell.org/package/text-1.2.3.2/docs/Data-Text.html#v:tail) as well 200 | 201 | #### [`init`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-List.html#v:init) 202 | 203 | Throws an exception in pure code when the input is an empty list. 204 | 205 | ``` 206 | Prelude> init [] 207 | *** Exception: Prelude.init: empty list 208 | ``` 209 | 210 | Use a case-match on the `reverse` of the list instead, but keep in mind that it uses linear time in the length of the list. 211 | Use a different data structure if that is an issue for you. 212 | Since `base-4.19` you can also employ [`unsnoc`](https://hackage.haskell.org/package/base/docs/Data-List.html#v:unsnoc). 213 | 214 | Applies to [Data.Text.init](https://hackage.haskell.org/package/text-1.2.3.2/docs/Data-Text.html#v:init) as well 215 | 216 | #### [`last`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-List.html#v:last) 217 | 218 | Throws an exception in pure code when the input is an empty list. 219 | 220 | ``` 221 | Prelude> last [] 222 | *** Exception: Prelude.last: empty list 223 | ``` 224 | 225 | Use a `listToMaybe . reverse` instead, but keep in mind that it uses linear time in the length of the list. 226 | Use a different data structure if that is an issue for you. 227 | Since `base-4.19` you can also employ [`unsnoc`](https://hackage.haskell.org/package/base/docs/Data-List.html#v:unsnoc). 228 | 229 | Applies to [Data.Text.last](https://hackage.haskell.org/package/text-1.2.3.2/docs/Data-Text.html#v:last) as well 230 | 231 | #### [`'!!'`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-List.html#v:-33--33-) 232 | 233 | Throws an exception in pure code when the index is out of bounds. 234 | 235 | ``` 236 | Prelude> [1, 2, 3] !! 3 237 | *** Exception: Prelude.!!: index too large 238 | ``` 239 | 240 | It also allows negative indices, for which it also throws. 241 | 242 | ``` 243 | Prelude> [1,2,3] !! (-1) 244 | *** Exception: Prelude.!!: negative index 245 | ``` 246 | 247 | The right way to index is to not use a list, because list indexing takes `O(n)` time, even if you find a safe way to do it. 248 | If you _really_ need to deal with _list_ indexing (you don't), then you can use a combination of [`take`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-List.html#v:take) and [`drop`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-List.html#v:drop) 249 | or (since `base-4.19`) [`'!?'`](https://hackage.haskell.org/package/base/docs/Data-List.html#v:-33--63-). 250 | 251 | #### [`fromJust`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-Maybe.html#v:fromJust) 252 | 253 | Throws an exception _in pure code_ when the input is `Nothing`. 254 | 255 | ``` 256 | Prelude Data.Maybe> fromJust Nothing 257 | *** Exception: Maybe.fromJust: Nothing 258 | CallStack (from HasCallStack): 259 | error, called at libraries/base/Data/Maybe.hs:148:21 in base:Data.Maybe 260 | fromJust, called at :11:1 in interactive:Ghci1 261 | ``` 262 | 263 | Use a case-match instead. 264 | 265 | #### [`read`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Text-Read.html#v:read) 266 | 267 | There are multiple reasons not to use `read`. 268 | The most obvious one is that it is partial. 269 | It throws an exception _in pure code_ whenever the input cannot be parsed (and doesn't even give a helpful parse error): 270 | 271 | ``` 272 | Prelude> read "a" :: Int 273 | *** Exception: Prelude.read: no parse 274 | ``` 275 | 276 | You can use `readMaybe` to get around this issue, HOWEVER: 277 | 278 | The second reason not to use `read` is that it operates on `String`. 279 | 280 | ``` haskell 281 | read :: Read a => String -> a 282 | ``` 283 | 284 | If you are doing any parsing, you should be using a more appropriate data type to parse: (`Text` or `ByteString`) 285 | 286 | The third reason is that `read` comes from [the `Read` type class](https://hackage.haskell.org/package/base/docs/Text-Read.html#t:Read), which has no well-defined semantics. 287 | In an ideal case, `read` and `show` would be inverses but this is _just not the reality_. 288 | See [`UTCTime`](https://hackage.haskell.org/package/time/docs/Data-Time-Clock.html#t:UTCTime) as an example. 289 | 290 | #### [`toEnum`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:toEnum) 291 | 292 | The `toEnum :: Enum => Int -> a` function is partial whenever the `Enum`erable type `a` is smaller than `Int`: 293 | 294 | ``` 295 | Prelude> toEnum 5 :: Bool 296 | *** Exception: Prelude.Enum.Bool.toEnum: bad argument 297 | Prelude Data.Word> toEnum 300 :: Word8 298 | *** Exception: Enum.toEnum{Word8}: tag (300) is outside of bounds (0,255) 299 | ``` 300 | 301 | #### [`succ`](https://hackage.haskell.org/package/base-4.15.0.0/docs/GHC-Enum.html#v:succ) and [`pred`](https://hackage.haskell.org/package/base-4.15.0.0/docs/GHC-Enum.html#v:pred) 302 | 303 | These are partial, on purpose. 304 | According to the docs: 305 | 306 | > The calls `succ maxBound` and `pred minBound` should result in a runtime error. 307 | 308 | ``` 309 | Prelude Data.Word> succ 255 :: Word8 310 | *** Exception: Enum.succ{Word8}: tried to take `succ' of maxBound 311 | Prelude Data.Word> pred 0 :: Word8 312 | *** Exception: Enum.pred{Word8}: tried to take `pred' of minBound 313 | ``` 314 | 315 | Use something like [`succMay`](https://hackage.haskell.org/package/safe-0.3.19/docs/Safe.html#v:succMay). 316 | 317 | #### Functions involving division 318 | 319 | * [`quot`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:quot) 320 | * [`div`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:div) 321 | * [`rem`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:rem) 322 | * [`mod`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:mod) 323 | * [`divMod`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:divMod) 324 | * [`quotRem`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:quotRem) 325 | 326 | ``` 327 | Prelude> quot 1 0 328 | *** Exception: divide by zero 329 | Prelude> minBound `quot` (-1) :: Int 330 | *** Exception: arithmetic overflow 331 | Prelude> div 1 0 332 | *** Exception: divide by zero 333 | Prelude> minBound `div` (-1) :: Int 334 | *** Exception: arithmetic overflow 335 | Prelude> rem 1 0 336 | *** Exception: divide by zero 337 | Prelude> mod 1 0 338 | *** Exception: divide by zero 339 | Prelude> divMod 1 0 340 | *** Exception: divide by zero 341 | Prelude> quotRem 1 0 342 | *** Exception: divide by zero 343 | ``` 344 | 345 | Whenever you consider using division, _really_ ask yourself whether you need division. 346 | For example, you can (almost always) replace ``a `div` 2 <= b`` by `a <= 2 * b`. 347 | (If you're worried about overflow, then use a bigger type.) 348 | 349 | If your use-case has a fixed (non-`0`) _literal_ denominator, like ``a `div` 2``, and you have already considered using something other than division, then your case constitutes an acceptable exception. 350 | 351 | Note that integer division may not be what you want in the first place anyway: 352 | 353 | ``` 354 | Prelude> 5 `div` 2 355 | 2 -- Not 2.5 356 | ``` 357 | 358 | See also https://github.com/NorfairKing/haskell-WAT#num-int 359 | 360 | #### [`minimum`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-List.html#v:minimum) and [`maximum`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-List.html#v:maximum) 361 | 362 | These functions throw an exception in pure code whenever the input is empty: 363 | 364 | ``` 365 | Prelude> minimum [] 366 | *** Exception: Prelude.minimum: empty list 367 | Prelude> maximum [] 368 | *** Exception: Prelude.maximum: empty list 369 | Prelude> minimum Nothing 370 | *** Exception: minimum: empty structure 371 | Prelude> minimum (Left "wut") 372 | *** Exception: minimum: empty structure 373 | Prelude Data.Functor.Const> minimum (Const 5 :: Const Int ()) 374 | *** Exception: minimum: empty structure 375 | ``` 376 | 377 | The same goes for [`minimumBy`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-List.html#v:minimumBy) and [`maximumBy`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-List.html#v:maximumBy). 378 | 379 | You can use [`minimumMay`](http://hackage.haskell.org/package/safe-0.3.19/docs/Safe.html#v:minimumMay) from the [`safe`](http://hackage.haskell.org/package/safe) package (or a case-match on the `sort`-ed version of your list, if you don't want an extra dependency). 380 | 381 | Applies to [Data.Text.maximum](https://hackage.haskell.org/package/text-1.2.3.2/docs/Data-Text.html#v:maximum) and [Data.Text.minimum](https://hackage.haskell.org/package/text-1.2.3.2/docs/Data-Text.html#v:minimum) as well 382 | 383 | #### [`Data.Text.Encoding.decodeUtf8`](http://hackage.haskell.org/package/text-1.2.4.1/docs/Data-Text-Encoding.html#v:decodeUtf8) 384 | 385 | Throws on invalid UTF-8 datao use `Data.Text.Encoding.decodeUtf8'` instead. 386 | 387 | ### Functions that throw exceptions in pure code on purpose 388 | 389 | #### [`throw`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Control-Exception.html#v:throw) 390 | 391 | Purposely throws an exception _in pure code_. 392 | 393 | ``` 394 | Prelude Control.Exception> throw $ ErrorCall "here be a problem" 395 | *** Exception: here be a problem 396 | ``` 397 | 398 | Don't throw from pure code, use throwIO instead. 399 | 400 | #### [`undefined`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:undefined) 401 | 402 | Purposely fails, with a particularly unhelpful error message. 403 | 404 | ``` 405 | Prelude> undefined 406 | *** Exception: Prelude.undefined 407 | CallStack (from HasCallStack): 408 | error, called at libraries/base/GHC/Err.hs:80:14 in base:GHC.Err 409 | undefined, called at :1:1 in interactive:Ghci1 410 | ``` 411 | 412 | Deal with errors appropriately instead. 413 | 414 | Also see `error` below. 415 | 416 | #### [`error`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:error) 417 | 418 | Purposely fails, with an only slightly less unhelpful error message than `undefined`. 419 | 420 | ``` 421 | Prelude> error "here be a problem" 422 | *** Exception: here be a problem 423 | CallStack (from HasCallStack): 424 | error, called at :4:1 in interactive:Ghci1 425 | ``` 426 | 427 | Deal with errors appropriately instead. 428 | 429 | If you're _really very extra sure_ that a certain case will never happen. 430 | Bubble up the error to the `IO` part of your code and then use `throwIO` or `die`. 431 | 432 | 433 | ### Functions that do unexpected things 434 | 435 | #### [`realToFrac`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:realToFrac) 436 | 437 | This function goes through `Rational`: 438 | 439 | ``` 440 | -- | general coercion to fractional types 441 | realToFrac :: (Real a, Fractional b) => a -> b 442 | realToFrac = fromRational . toRational 443 | ``` 444 | 445 | `Rational` does not have all the values that a `Real` like `Double` might have, so things will go wrong in ways that you don't expect: 446 | 447 | ``` 448 | Prelude> realToFrac nan :: Double 449 | -Infinity 450 | ``` 451 | 452 | Avoid general coercion functions and anything to do with `Double` in particular. 453 | 454 | See also https://github.com/NorfairKing/haskell-WAT#real-double 455 | 456 | #### [`%`](https://hackage.haskell.org/package/base-4.16.1.0/docs/Data-Ratio.html#v:-37-): Rational values 457 | 458 | The `%` function is used to construct rational values: 459 | 460 | ``` haskell 461 | data Ratio a = !a :% !a deriving Eq 462 | ``` 463 | ``` haskell 464 | Prelude Data.Int Data.Ratio> 1 % 12 :: Ratio Int8 465 | 1 % 12 466 | ``` 467 | 468 | There are constraints on the two values in Rational values: 469 | 470 | Recall (from the docs); "The numerator and denominator have no common factor and the denominator is positive." 471 | 472 | When using fixed-size underlying types, you can end up with invalid `Ratio` values using `Num` functions: 473 | 474 | ``` 475 | Prelude Data.Int Data.Ratio> let r = 1 % 12 :: Ratio Int8 476 | Prelude Data.Int Data.Ratio> r - r 477 | 0 % (-1) 478 | Prelude Data.Int Data.Ratio> r + r 479 | 3 % (-14) 480 | > r * r 481 | 1 % (-112) 482 | ``` 483 | 484 | When using arbitrarily-sized underlying types, you can end up with arbitrary runtime: 485 | 486 | ``` 487 | (1 % 100)^10^10^10 :: Rational -- Prepare a way to kill this before you try it out. 488 | ``` 489 | 490 | `Ratio` values create issues for any underlying type, so avoid them. 491 | Consider whether you really need any rational values. 492 | If you _really_ do, and you have a clear maximum value, consider using fixed-point values. 493 | If that does not fit your use-case, consider using `Double` with all its caveats. 494 | 495 | #### [`fromIntegral`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:fromIntegral) and [`fromInteger`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:fromInteger) 496 | 497 | `fromIntegral` has no constraints on the size of the output type, so that output type could be smaller than the input type. 498 | In such a case, it performs silent truncation: 499 | ``` 500 | > fromIntegral (300 :: Word) :: Word8 501 | 44 502 | ``` 503 | Similarly for `fromInteger`: 504 | ``` 505 | > fromInteger 300 :: Word8 506 | 44 507 | ``` 508 | 509 | `fromIntegral` has also had some very nasty bugs that involved the function behaving differently (even partially) depending on optimisation levels. 510 | See [GHC #20066](https://gitlab.haskell.org/ghc/ghc/-/issues/20066) and [GHC #19345](https://gitlab.haskell.org/ghc/ghc/-/issues/19345). 511 | 512 | Avoid general coercion functions but write specific ones instead, as long as the type of the result is bigger than the type of the input. 513 | 514 | ``` 515 | word32ToWord64 :: Word32 -> Word64 516 | word32ToWord64 = fromIntegral -- Safe because Word64 is bigger than Word32 517 | ``` 518 | 519 | Prefer to use functions with non-parametric types and/or functions that fail loudly, like these: 520 | 521 | * [`naturalToInteger :: Natural -> Integer`](https://hackage.haskell.org/package/base-4.15.0.0/docs/GHC-Natural.html#v:naturalToInteger) 522 | * [`naturalToWord :: Natural -> Maybe Word`](https://hackage.haskell.org/package/base-4.15.0.0/docs/GHC-Natural.html#v:naturalToWordMaybe) 523 | * [`toIntegralSized`](http://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Bits.html#v:toIntegralSized) 524 | 525 | 526 | Witness the trail of destruction: 527 | 528 | * [Bug in `System.IO.hWaitForInput`](http://haskell.1045720.n5.nabble.com/Deprecating-fromIntegral-tt5864578.html) because of `fromIntegral` 529 | * [Bug in cryptography-related code](https://github.com/haskell-crypto/cryptonite/issues/330) because of `fromIntegral` 530 | 531 | I was also pointed to the [`finitary`](http://hackage.haskell.org/package/finitary) package but I haven't used it yet. 532 | 533 | #### [`toEnum`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:toEnum) 534 | 535 | The `toEnum` function suffers from the following problem **on top of being partial**. 536 | 537 | Some instances of `Enum` use "the next constructor" as the next element while others use a `n+1` variant: 538 | 539 | ``` 540 | Prelude> toEnum 5 :: Double 541 | 5.0 542 | Prelude Data.Fixed> toEnum 5 :: Micro 543 | 0.000005 544 | ``` 545 | 546 | Depending on what you expected, one of those doesn't do what you think it does. 547 | 548 | #### [`fromEnum`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelude.html#v:fromEnum) 549 | 550 | From the docs: 551 | 552 | > It is implementation-dependent what fromEnum returns when applied to a value that is too large to fit in an Int. 553 | 554 | For example, some `Integer` that does not fit in an `Int` will be mapped to `0`, some will be mapped all over the place 555 | 556 | ``` 557 | Prelude> fromEnum (2^66 :: Integer) -- To 0 558 | 0 559 | Prelude> fromEnum (2^65 :: Integer) -- To 0 560 | 0 561 | Prelude> fromEnum (2^64 :: Integer) -- To 0 562 | 0 563 | Prelude> fromEnum (2^64 -1 :: Integer) -- To -1 ?! 564 | 0 565 | Prelude> fromEnum (2^63 :: Integer) -- To -2^63 ?! 566 | -9223372036854775808 567 | ``` 568 | 569 | This is because `fromEnum :: Integer -> Int` is implemented using [`integerToInt`](https://hackage.haskell.org/package/integer-gmp-0.5.1.0/docs/GHC-Integer.html) which treats big integers and small integers differently. 570 | 571 | #### [`succ`](https://hackage.haskell.org/package/base-4.15.0.0/docs/GHC-Enum.html#v:succ) and [`pred`](https://hackage.haskell.org/package/base-4.15.0.0/docs/GHC-Enum.html#v:pred) 572 | 573 | These suffer from the same problem as `toEnum` (see above) **on top of being partial**. 574 | 575 | ``` 576 | Prelude> succ 5 :: Double 577 | 6.0 578 | Prelude Data.Fixed> succ 5 :: Micro 579 | 5.000001 580 | Prelude> pred 0 :: Word 581 | *** Exception: Enum.pred{Word}: tried to take `pred' of minBound 582 | Prelude Data.Ord Data.Int> succ (127 :: Int8) 583 | *** Exception: Enum.succ{Int8}: tried to take `succ' of maxBound 584 | ``` 585 | 586 | #### `fromString` on `ByteString` 587 | 588 | When converting to `ByteString`, `fromString` silently truncates to the bottom eight bits, turning your string into garbage. 589 | 590 | ```haskell 591 | > print "⚠" 592 | "\9888" 593 | > print (fromString "⚠" :: ByteString) 594 | "\160" 595 | ``` 596 | 597 | ### The [`enumFromTo`](https://hackage.haskell.org/package/base-4.15.0.0/docs/GHC-Enum.html#v:enumFromTo)-related functions 598 | 599 | These also suffer from the same problem as `toEnum` (see above) 600 | 601 | ``` 602 | Prelude> succ 5 :: Int 603 | 6 604 | Prelude Data.Fixed> succ 5 :: Micro 605 | 5.000001 606 | ``` 607 | 608 | ### Functions related to `String`-based IO 609 | 610 | #### Input 611 | 612 | - `System.IO.getChar` 613 | - `System.IO.getLine` 614 | - `System.IO.getContents` 615 | - `System.IO.interact` 616 | - `System.IO.readIO` 617 | - `System.IO.readLn` 618 | - `System.IO.readFile` 619 | 620 | These behave differently depending on env vars, and actually fail on non-text data in files: 621 | 622 | ``` 623 | Prelude> readFile "example.dat" 624 | *** Exception: Test/A.txt: hGetContents: invalid argument (invalid byte sequence) "\226\8364 625 | ``` 626 | 627 | See also [this blogpost](https://www.snoyman.com/blog/2016/12/beware-of-readfile/). 628 | 629 | Use `ByteString`-based input and then use `Data.Text.Encoding.decodeUtf8'` if necessary. (But not `Data.Text.Encoding.decodeUtf8`, see above.) 630 | 631 | #### Output 632 | 633 | - `System.IO.putChar` 634 | - `System.IO.putStr` 635 | - `System.IO.putStrLn` 636 | - `System.IO.print` 637 | - `System.IO.writeFile` 638 | - `System.IO.appendFile` 639 | 640 | These behave differently depending on env vars: 641 | 642 | ``` 643 | $ ghci 644 | Prelude> putStrLn "\973" 645 | ύ 646 | ``` 647 | 648 | but 649 | ``` 650 | $ export LANG=C 651 | $ export LC_ALL=C 652 | $ ghci 653 | Prelude> putStrLn "\973" 654 | ? 655 | ``` 656 | 657 | 658 | Use `ByteString`-based output, on encoded `Text` values or directly on bytestrings instead. 659 | 660 | `writeFile` caused a real-world outage for @tomjaguarpaw on 2021-09-24. 661 | 662 | See also [this blogpost](https://www.snoyman.com/blog/2016/12/beware-of-readfile/). 663 | 664 | ### Functions related to `Text`-based IO 665 | 666 | - `Data.Text.IO.readFile` 667 | - `Data.Text.IO.Lazy.readFile` 668 | 669 | These have the same issues as `readFile`. 670 | 671 | See also [this blogpost](https://www.snoyman.com/blog/2016/12/beware-of-readfile/). 672 | 673 | Since `text-2.1` one can replace `Data.Text.IO` with [`Data.Text.IO.Utf8`](https://hackage.haskell.org/package/text-2.1/docs/Data-Text-IO-Utf8.html). 674 | 675 | 676 | ### Functions with unexpected performance characteristics 677 | 678 | #### `nub` 679 | 680 | `O(n^2)`, use [`ordNub`](https://github.com/nh2/haskell-ordnub) instead 681 | 682 | Trail of destruction: 683 | https://gitlab.haskell.org/ghc/ghc/-/issues/8173#note_236901 684 | 685 | #### `foldl` and `foldMap` 686 | 687 | Lazy. Use `foldl'` and `foldMap'` instead. 688 | 689 | See [this excellent explanation](https://github.com/hasura/graphql-engine/pull/2933#discussion_r328821960). 690 | 691 | #### `sum` and `product` 692 | Lazy accumulator, but is fixed as of [GHC 9.0.1](https://gitlab.haskell.org/ghc/ghc/-/merge_requests/4675). 693 | 694 | #### `genericLength` 695 | 696 | `genericLength` consumes O(n) stack space when returning a strict numeric type. Lazy numeric types (e.g. `data Nat = Z | S Nat`) are *very* rare in practice so `genericLength` is probably not what you want. 697 | 698 | 699 | ### Confusing functions 700 | 701 | These functions are a _bad idea_ for no other reason than readability. 702 | If there is a bug that involves these functions, it will be really easy to read over them. 703 | 704 | #### `unless` 705 | 706 | 707 | `unless` is defined as follows: 708 | 709 | ``` 710 | unless p s = if p then pure () else s 711 | ``` 712 | 713 | This is really confusing in practice use [`when`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Control-Monad.html#v:when) with `not` instead. 714 | 715 | #### `either` 716 | 717 | Either takes two functions as arguments, one for the `Left` case and one for the `Right` case. 718 | Which comes first? I don't know either, just use a case-match instead. 719 | 720 | 721 | ### Modules or packages to avoid 722 | 723 | These are debatable, but requiring a good justification for using them is a good default. 724 | 725 | #### [`Control.Lens`](https://hackage.haskell.org/package/lens) 726 | 727 | The `lens` package is full of abstract nonsense and obscure operators. 728 | There are good reasons (in exceptional cases) for using it, like in [`cursor`](https://github.com/NorfairKing/cursor), for example, but it should be avoided in general. 729 | 730 | It also has an ENORMOUS dependency footprint. 731 | 732 | If you need to use a dependency that uses lenses without the `lens` dependency, you can use `microlens` to stick with the (relatively) simple parts of lenses. 733 | If you need to use a dependency that uses `lens`, go ahead and use `lens`, but stick with `view` (`^.`) and `set` (`.~`). 734 | 735 | ### Extensions to avoid 736 | 737 | These are also debatable and low priority compared to the rest in this document, but still interesting to consider 738 | 739 | #### [`{-# LANGUAGE DeriveAnyClass #-}`](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/derive_any_class.html#extension-DeriveAnyClass) 740 | 741 | Just use 742 | 743 | ``` haskell 744 | data MyExample 745 | instance MyClass MyExample 746 | ``` 747 | 748 | instead of 749 | 750 | ``` haskell 751 | data MyExample 752 | deriving MyClass 753 | ``` 754 | 755 | [GHC (rather puzzlingly) gives the recommendation to turn on `DeriveAnyClass` even when that would lead to code that throws an exception in pure code at runtime.](https://gitlab.haskell.org/ghc/ghc/-/issues/19692) 756 | As a result, banning this extension brings potentially great upside: preventing a runtime exception, as well as reducing confusion, for the cost of writing a separate line for an instance if you know what you are doing. 757 | 758 | See also [this great explainer video by Tweag](https://www.youtube.com/watch?v=Zdne-Ch2000). 759 | 760 | #### [`{-# LANGUAGE TupleSections #-}`](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/tuple_sections.html) 761 | 762 | This lets you add `{-# LANGUAGE TupleSections #-}` and potential confusion to write `(, v)` instead of `\a -> (a, v)`. 763 | 764 | Whenever you feel the need to use `TupleSections`, you probably want to be using a data type instead of tuples instead. 765 | 766 | #### [`{-# LANGUAGE DuplicateRecordFields #-}`](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/duplicate_record_fields.html) 767 | 768 | To keep things simple, use prefix-named record fields like this: 769 | 770 | ``` haskell 771 | data Template = Template { templateName :: Text, templateContents :: Text } 772 | ``` 773 | 774 | instead of this 775 | 776 | ``` haskell 777 | {-# LANGUAGE DuplicateRecordFields #-} 778 | data Template = Template { name :: Text, contents :: Text } 779 | ``` 780 | 781 | It may be more typing but it makes code a lot more readable. 782 | 783 | If you are concerned about not being able to auto-derive `aeson`'s `ToJSON` and `FromJSON` instances anymore, 784 | you shouldn't be. You can still that using something like [`aeson-casing`](https://hackage.haskell.org/package/aeson-casing). 785 | It's also dangerous to have serialisation output depend on the naming of things in your code, so be sure to test your serialisation with both property tests via [`genvalidity-sydtest-aeson`](https://hackage.haskell.org/package/genvalidity-sydtest-aeson) and golden tests via [`sydtest-aeson`](https://github.com/NorfairKing/sydtest). 786 | 787 | 788 | #### [`{-# LANGUAGE NamedFieldPuns #-}`](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/record_puns.html#extension-NamedFieldPuns) 789 | 790 | Introduces unnecessary syntax. 791 | 792 | For this example: 793 | ``` haskell 794 | data C = C { a :: Int } 795 | ``` 796 | 797 | just use this: 798 | 799 | 800 | ``` haskell 801 | f c = foo (a c) 802 | ``` 803 | 804 | instead of this: 805 | 806 | ``` haskell 807 | f (C {a}) = foo a 808 | ``` 809 | 810 | #### [`{-# LANGUAGE OverloadedLabels #-}`](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_labels.html#extension-OverloadedLabels) 811 | 812 | If you're using this, you either know what you're doing - in which case you should know better than to use this - or you don't - in which case you definitely shouldn't use it. 813 | Keep your code simple and just use record field selectors instead. 814 | 815 | This extension often goes hand in hand with lens usage, which should also be discouraged, see above. 816 | 817 | ### Unsafe functions 818 | #### `unsafePerformIO` 819 | 820 | Before you use this function, first read [its documentation](http://hackage.haskell.org/package/base-4.14.1.0/docs/System-IO-Unsafe.html#v:unsafePerformIO) carefully. 821 | If you've done (and I know you haven't, you liar) and still want to use it, read the following section first. 822 | 823 | 824 | When you use `unsafePerformIO`, you pinkie-promise that the code in the `IO a` that you provide is definitely always 100% pure, you swear. 825 | If this is not the case, all sorts of assumptions don't work anymore. 826 | For example, if the code that you want to execute in `unsafePerformIO` is not evaluated, then the IO is never executed: 827 | 828 | ``` haskell 829 | Prelude> fmap (const 'o') $ putStrLn "hi" 830 | hi 831 | 'o' 832 | Prelude System.IO.Unsafe> const 'o' $ unsafePerformIO $ putStrLn "hi" 833 | 'o' 834 | ``` 835 | 836 | Another issue is that pure code can be inlined whereas IO-based code cannot. When you pinkie-promise that your code is "morally" pure, you also promise that inlining it will not cause trouble. 837 | This is not true in general: 838 | 839 | ``` 840 | Prelude System.IO.Unsafe> let a = unsafePerformIO $ putStrLn "hi" 841 | Prelude System.IO.Unsafe> a 842 | hi 843 | () 844 | Prelude System.IO.Unsafe> a 845 | () 846 | ``` 847 | 848 | Lastly, this function is also not type-safe, as you can see here: 849 | 850 | ``` 851 | $ cat file.hs 852 | ``` 853 | 854 | ``` haskell 855 | import Data.IORef 856 | import System.IO.Unsafe 857 | 858 | test :: IORef [a] 859 | test = unsafePerformIO $ newIORef [] 860 | 861 | main = do 862 | writeIORef test [42] 863 | bang <- readIORef test 864 | print $ map (\f -> f 5 6) (bang :: [Int -> Int -> Int]) 865 | ``` 866 | 867 | ``` 868 | $ runhaskell file.hs 869 | [file.hs: internal error: stg_ap_pp_ret 870 | (GHC version 8.8.4 for x86_64_unknown_linux) 871 | Please report this as a GHC bug: https://www.haskell.org/ghc/reportabug 872 | [1] 13949 abort (core dumped) runhaskell file.hs 873 | ``` 874 | 875 | #### [`unsafeDupablePerformIO`](http://hackage.haskell.org/package/base-4.14.1.0/docs/System-IO-Unsafe.html#v:unsafeDupablePerformIO) 876 | 877 | Like `unsafePerformIO` but is even less safe. 878 | 879 | #### [`unsafeInterleaveIO`](http://hackage.haskell.org/package/base-4.14.1.0/docs/System-IO-Unsafe.html#v:unsafeInterleaveIO) 880 | 881 | Used to define lazy IO, which should be avoided. 882 | 883 | #### [`unsafeFixIO`](http://hackage.haskell.org/package/base-4.14.1.0/docs/System-IO-Unsafe.html#v:unsafeFixIO) 884 | 885 | Unsafe version of `fixIO`. 886 | 887 | #### [`unsafeCoerce`](https://hackage.haskell.org/package/base-4.21.0.0/docs/Unsafe-Coerce.html#v:unsafeCoerce) 888 | 889 | Can cause segfaults. All bets are off: 890 | 891 | ``` 892 | ghci> (unsafeCoerce (5 :: Int) :: (Int -> String)) 5 893 | "[1] 3415638 segmentation fault (core dumped) ghci 894 | ``` 895 | 896 | ### Deprecated 897 | 898 | #### `return` 899 | 900 | Use `pure` instead. 901 | See https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/monad-of-no-return 902 | 903 | -------------------------------------------------------------------------------- /concat-benchmark.hs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env stack 2 | -- stack --resolver lts-16.31 script 3 | 4 | import Control.DeepSeq 5 | import Criterion.Main 6 | 7 | -- Concat looks linear 8 | -- benchmarking concat/256 9 | -- time 2.474 μs (2.466 μs .. 2.484 μs) 10 | -- 1.000 R² (1.000 R² .. 1.000 R²) 11 | -- mean 2.485 μs (2.472 μs .. 2.498 μs) 12 | -- std dev 45.33 ns (37.19 ns .. 55.72 ns) 13 | -- variance introduced by outliers: 19% (moderately inflated) 14 | -- 15 | -- benchmarking concat/1024 16 | -- time 10.45 μs (10.35 μs .. 10.57 μs) 17 | -- 0.999 R² (0.999 R² .. 1.000 R²) 18 | -- mean 10.41 μs (10.33 μs .. 10.48 μs) 19 | -- std dev 243.1 ns (204.8 ns .. 296.0 ns) 20 | -- variance introduced by outliers: 25% (moderately inflated) 21 | -- 22 | -- benchmarking concat/4096 23 | -- time 41.92 μs (41.67 μs .. 42.23 μs) 24 | -- 1.000 R² (0.999 R² .. 1.000 R²) 25 | -- mean 42.02 μs (41.76 μs .. 42.33 μs) 26 | -- std dev 991.6 ns (790.8 ns .. 1.260 μs) 27 | -- variance introduced by outliers: 22% (moderately inflated) 28 | 29 | benchConcatRoot :: Int -> Benchmark 30 | benchConcatRoot i = bench (show i) $ xs `seq` nf concat xs 31 | where 32 | root = floor (sqrt (fromIntegral i)) :: Int 33 | individualList = replicate root (0.0 :: Double) 34 | totalList = replicate root individualList 35 | xs = force totalList 36 | 37 | -- benchmarking concat/16 38 | -- time 3.772 μs (3.761 μs .. 3.786 μs) 39 | -- 1.000 R² (1.000 R² .. 1.000 R²) 40 | -- mean 3.809 μs (3.789 μs .. 3.836 μs) 41 | -- std dev 83.20 ns (65.32 ns .. 117.2 ns) 42 | -- variance introduced by outliers: 24% (moderately inflated) 43 | -- 44 | -- benchmarking concat/32 45 | -- time 8.904 μs (8.557 μs .. 9.340 μs) 46 | -- 0.987 R² (0.982 R² .. 0.992 R²) 47 | -- mean 8.717 μs (8.505 μs .. 9.043 μs) 48 | -- std dev 882.2 ns (711.8 ns .. 1.230 μs) 49 | -- variance introduced by outliers: 87% (severely inflated) 50 | -- 51 | -- benchmarking concat/64 52 | -- time 16.73 μs (16.57 μs .. 17.01 μs) 53 | -- 0.999 R² (0.997 R² .. 1.000 R²) 54 | -- mean 16.69 μs (16.56 μs .. 16.83 μs) 55 | -- std dev 453.9 ns (325.9 ns .. 670.8 ns) 56 | -- variance introduced by outliers: 29% (moderately inflated) 57 | 58 | benchConcatManyLists :: Int -> Benchmark 59 | benchConcatManyLists i = bench (show i) $ xs `seq` nf concat xs 60 | where 61 | individualList = replicate 25 (0.0 :: Double) 62 | totalList = replicate i individualList 63 | xs = force totalList 64 | 65 | -- benchmarking benchConcatLongLists/16 66 | -- time 3.896 μs (3.876 μs .. 3.917 μs) 67 | -- 1.000 R² (0.999 R² .. 1.000 R²) 68 | -- mean 3.893 μs (3.877 μs .. 3.922 μs) 69 | -- std dev 69.18 ns (48.06 ns .. 115.7 ns) 70 | -- variance introduced by outliers: 17% (moderately inflated) 71 | -- 72 | -- benchmarking benchConcatLongLists/32 73 | -- time 8.111 μs (8.068 μs .. 8.150 μs) 74 | -- 1.000 R² (0.999 R² .. 1.000 R²) 75 | -- mean 8.106 μs (8.068 μs .. 8.155 μs) 76 | -- std dev 140.6 ns (112.6 ns .. 189.7 ns) 77 | -- variance introduced by outliers: 16% (moderately inflated) 78 | -- 79 | -- benchmarking benchConcatLongLists/64 80 | -- time 16.61 μs (16.38 μs .. 16.92 μs) 81 | -- 0.999 R² (0.997 R² .. 1.000 R²) 82 | -- mean 16.50 μs (16.39 μs .. 16.68 μs) 83 | -- std dev 461.9 ns (304.2 ns .. 805.3 ns) 84 | -- variance introduced by outliers: 31% (moderately inflated) 85 | benchConcatLongLists :: Int -> Benchmark 86 | benchConcatLongLists i = bench (show i) $ xs `seq` nf concat xs 87 | where 88 | individualList = replicate i (0.0 :: Double) 89 | totalList = replicate 25 individualList 90 | xs = force totalList 91 | 92 | main :: IO () 93 | main = 94 | defaultMain 95 | [ bgroup 96 | "benchConcatRoot" 97 | [benchConcatRoot (4 ^ i) | i <- [4, 5, 6]], 98 | bgroup 99 | "benchConcatManyLists" 100 | [benchConcatManyLists (2 ^ i) | i <- [4, 5, 6]], 101 | bgroup 102 | "benchConcatLongLists" 103 | [benchConcatLongLists (2 ^ i) | i <- [4, 5, 6]] 104 | ] 105 | -------------------------------------------------------------------------------- /hlint.yaml: -------------------------------------------------------------------------------- 1 | # Haskell's Dangerous Functions 2 | # For a how-to-use and the latest version of this file go to: 3 | # https://github.com/NorfairKing/haskell-dangerous-functions/ 4 | 5 | - ignore: { name: "Use unless" } 6 | - ignore: { name: "Use tuple-section" } 7 | 8 | - functions: 9 | - {name: unsafeDupablePerformIO, within: []} # Unsafe 10 | - {name: unsafeInterleaveIO, within: []} # Unsafe 11 | - {name: unsafeFixIO, within: []} # Unsafe 12 | - {name: unsafePerformIO, within: []} # Unsafe 13 | - {name: unsafeCoerce, within: []} # Unsafe 14 | 15 | # _VERY_ hard to get right, use the async library instead. 16 | # See also https://github.com/informatikr/hedis/issues/165 17 | - {name: forkIO, within: []} 18 | # Mostly impossible to get right, rethink what you're doing entirely. 19 | # See also https://www.reddit.com/r/haskell/comments/jsap9r/how_dangerous_is_forkprocess/ 20 | - {name: forkProcess, within: []} 21 | 22 | - {name: undefined, within: []} # Purposely fails. Deal with errors appropriately instead. 23 | - {name: throw, within: []} # Don't throw from pure code, use throwIO instead. 24 | - {name: Prelude.error, within: []} 25 | 26 | - {name: Data.List.head, within: []} # Partial, use `listToMaybe` instead. 27 | - {name: Data.List.tail, within: []} # Partial 28 | - {name: Data.List.init, within: []} # Partial 29 | - {name: Data.List.last, within: []} # Partial 30 | - {name: 'Data.List.!!', within: []} # Partial 31 | - {name: Data.List.genericIndex, within: []} # Partial 32 | - {name: Data.List.genericLength, within: []} 33 | 34 | # Same, but for Data.Text 35 | - {name: Data.Text.head, within: []} 36 | - {name: Data.Text.tail, within: []} 37 | - {name: Data.Text.init, within: []} 38 | - {name: Data.Text.last, within: []} 39 | 40 | - {name: minimum, within: []} # Partial 41 | - {name: minimumBy, within: []} # Partial 42 | - {name: maximum, within: []} # Partial 43 | - {name: maximumBy, within: []} # Partial 44 | 45 | # Same, but for Data.Text 46 | - {name: Data.Text.maximum, within: []} 47 | - {name: Data.Text.minimum, within: []} 48 | 49 | - {name: GHC.Enum.pred, within: []} # Partial 50 | - {name: GHC.Enum.succ, within: []} # Partial 51 | - {name: GHC.Enum.toEnum, within: []} # Partial 52 | - {name: GHC.Enum.fromEnum, within: []} # Does not do what you think it does. 53 | - {name: GHC.Enum.enumFrom, within: []} # Does not do what you think it does, depending on the type. 54 | - {name: GHC.Enum.enumFromThen, within: []} # Does not do what you think it does, depending on the type. 55 | - {name: GHC.Enum.enumFromTo, within: []} # Does not do what you think it does, depending on the type. 56 | - {name: GHC.Enum.enumFromThenTo, within: []} # Does not do what you think it does, depending on the type. 57 | 58 | - {name: unless, within: []} # Really confusing, use 'when' instead. 59 | - {name: either, within: []} # Really confusing, just use a case-match. 60 | 61 | - {name: nub, within: []} # O(n^2) 62 | 63 | - {name: Data.Foldable.foldl, within: []} # Lazy accumulator. Use foldl' instead. 64 | - {name: Data.Foldable.foldMap, within: []} # Lazy accumulator. Use foldMap' instead. 65 | - {name: Data.Foldable.sum, within: []} # Lazy accumulator 66 | - {name: Data.Foldable.product, within: []} # Lazy accumulator 67 | 68 | # Functions involving division 69 | - {name: Prelude.quot, within: []} # Partial, see https://github.com/NorfairKing/haskell-WAT#num-int 70 | - {name: Prelude.div, within: []} 71 | - {name: Prelude.rem, within: []} 72 | - {name: Prelude.mod, within: []} 73 | - {name: Prelude.quotRem, within: []} 74 | - {name: Prelude.divMod, within: []} 75 | 76 | # Does unexpected things, see 77 | # https://github.com/NorfairKing/haskell-WAT#real-double 78 | - {name: realToFrac, within: []} 79 | 80 | # Constructs rationals, which is either wrong or a bad idea. 81 | - {name: 'Data.Ratio.%', within: []} 82 | 83 | # Don't use string for command-line output. 84 | - {name: System.IO.putChar, within: []} 85 | - {name: System.IO.putStr, within: []} 86 | - {name: System.IO.putStrLn, within: []} 87 | - {name: System.IO.print, within: []} 88 | 89 | # Don't use string for command-line input either. 90 | - {name: System.IO.getChar, within: []} 91 | - {name: System.IO.getLine, within: []} 92 | - {name: System.IO.getContents, within: []} # Does lazy IO. 93 | - {name: System.IO.interact, within: []} 94 | - {name: System.IO.readIO, within: []} 95 | - {name: System.IO.readLn, within: []} 96 | 97 | # Don't use strings to interact with files 98 | - {name: System.IO.readFile, within: []} 99 | - {name: System.IO.writeFile, within: []} 100 | - {name: System.IO.appendFile, within: []} 101 | 102 | # Can succeed in dev, but fail in prod, because of encoding guessing 103 | # It's also Lazy IO. 104 | # See https://www.snoyman.com/blog/2016/12/beware-of-readfile/ for more info. 105 | - {name: Data.Text.IO.readFile, within: []} 106 | - {name: Data.Text.IO.Lazy.readFile, within: []} 107 | 108 | - {name: Data.Text.Encoding.decodeUtf8, within: []} # Throws on invalid UTF8 109 | 110 | - {name: fromJust, within: [], message: 'Partial'} # Partial 111 | 112 | # Does silent truncation: 113 | # > fromIntegral (300 :: Word) :: Word8 114 | # 44 115 | - {name: fromIntegral, within: []} 116 | - {name: fromInteger, within: []} 117 | 118 | 119 | - {name: 'read', within: []} # Partial, use `Text.Read.readMaybe` instead. 120 | 121 | # Deprecated, use `pure` instead. 122 | # See https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/monad-of-no-return 123 | - {name: 'return', within: []} 124 | 125 | - modules: 126 | - { name: Control.Lens, within: [] } 127 | 128 | - extensions: 129 | - { name: DeriveAnyClass, within: [] } # Dangerous 130 | 131 | - { name: DuplicateRecordFields, within: [] } 132 | 133 | - { name: NamedFieldPuns, within: [] } 134 | - { name: TupleSections, within: [] } 135 | - { name: OverloadedLabels, within: [] } 136 | --------------------------------------------------------------------------------