← Back to context

Comment by kreyenborgi

16 days ago

Don't think of it as being all pure code, think of it as tracking in the type system which parts of your code may launch the missiles and which parts can't. Given the following program,

    main :: IO ()
    main = do
      coordinates <- getCoords
      launch trajectory
      where
        trajectory = calcTrajectory coordinates
    
    getCoords :: IO Coordinates
    getCoords = -- TODO
    
    launch :: Trajectory -> IO  ()
    launch = -- TODO
    
    calcTrajectory :: Coordinates -> Trajectory
    calcTrajectory = -- TODO
    

I can look at the types and be reasonably certain that calcTrajectory does no reads/writes to disk or the network or anything of that sort (the part after the last arrow isn't `IO something`), the only side effect is perhaps to heat up the CPU a bit.

This also nudges you in the direction of an Functional Core, Imperative Shell architecture https://www.destroyallsoftware.com/screencasts/catalog/funct...

>as tracking in the type system which parts of your code may launch the missiles

given that Haskell is lazy by default there's a million ways to shoot yourself in the foot through memory leaks and performance issues (which is not unlike the problems the IO type attempts to make explicit in that domain), so I never really understand this kind of thing. Purity doesn't say much about safety or semantics of your code. By that logic you might as well introduce a recursion type and now you're tagging everything that is recursive because you can easily kill your program with an unexpected input in a recursive function. To me this is just semantics you have to think through anyway, putting this into the type system just ends up creating more convoluted programs.