Purity vs Effects

In Haskell there is a clear separation, which is applied through the typing system and the compiler, between pure code: it is always evaluated with the same output value given the same input and does not cause any side effects such as mutation of mutable objects or output to I/O devices; and code that produces effects:

Parent calls child Parent with effects Parent Pure
Child with effects Code with effects Compiler error
Child pure Code with effects Pure code

In some cases, to increase performance, this clear separation can somehow be bypassed with referential transparency. For example:

λ> import System.IO.Unsafe
λ> reftrans = unsafePerformIO $ pure =<< getChar
λ> :t reftrans
λ> reftrans :: Char -- IO effects can't be seen in the signature

When this happens, we can no longer see the side-effects in the function signatures and the type system and compiler, can’t no longer help us.

Note: All Haskell applications have a parental code branch with input and output I/O effects. If this were not the case, we could not provide inputs or see the output of the calculation and, therefore, it would be a waste of time to execute any application

Isolation and granulation

In Haskell, the bridge that is responsible for binding the pure code in combination the with code containing effects, is called monads.

Monads are structures that represent calculations defined as a sequence of steps.

As mentioned earlier, this bridge can do so gradually allowing us to make sure that if we allow a part of the code that can access the network, you can only perform that side-effect and not others.

In addition, we also want to provide the possibility to exclude packages that can’t be registered as trusted. This is achieved by introducing the concept of restricted effects, as described in the article [Safe {H}askel], to make sure that only a minimum number of effects can be used. An example combining both granulated as well as restricted effects can be seen in the following lines of code:

{-# LANGUAGE Safe #-}

--------------------------------------------------------------------------------

module Uniprocess.Effects.Granulated (..) where

--------------------------------------------------------------------------------

class (Monad m) => CmdM m where
  out :: String -> m ()
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}

--------------------------------------------------------------------------------

module Uniprocess.Effects.Restricted ( .. ) where

--------------------------------------------------------------------------------

newtype RIO a = RestrictedIO { rio :: IO a }

instance Functor RIO where
  fmap f m = RestrictedIO $      f <$> rio m

instance Applicative RIO where
  pure      = RestrictedIO . pure
  (<*>) f m = RestrictedIO $ rio f <*> rio m

instance Monad RIO where
  return    = RestrictedIO . return
  (>>=) m f = RestrictedIO $ rio m >>= rio . f

--------------------------------------------------------------------------------

class Granulated.CmdM m => CmdM m where
  out :: String -> m ()

instance Granulated.CmdM RIO => CmdM RIO where
  out x = RestrictedIO $ putStrLn $ x ++ " [Restricted]"
{-# LANGUAGE Safe #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}

--------------------------------------------------------------------------------

module Uniprocess.Effects.Granulated.Instances where

--------------------------------------------------------------------------------

instance Granulated.CmdM Restricted.RIO where
  out x =
    Restricted.out $ x ++ " [Granulated]"
{-# LANGUAGE Trustworthy #-}

--------------------------------------------------------------------------------

module Main (main) where

--------------------------------------------------------------------------------

isolated :: Eff.RIO ()
main     :: IO ()

--------------------------------------------------------------------------------

isolated = Uni.process

--------------------------------------------------------------------------------

main = Eff.rio . isolated
{-# LANGUAGE Safe #-}

--------------------------------------------------------------------------------

module Uniprocess.Process ( process ) where

--------------------------------------------------------------------------------

process :: ( Restricted.CmdM m ) => m ()

--------------------------------------------------------------------------------

process =
  Granulated.out "Uniprocess with isolated (and granulated) side-effects."

This approach is well known in information security and computer science as principle of least privilege (PoLP) where a process, a user, or a program (depending on the subject) must be able to access only the information and resources that are necessary for its legitimate purpose. Haskell, among very few, can enforce this at compile-time.

Therefore, it is very easy to ensure that the design and architecture will be applied throughout the entire application.

It will also be easy to see for the experts and maybe even for the users, that the application really does what it was designed to do.

And if someone tries to modify the application, with bad intentions, it will require major changes in the design and architecture, which can be easily spotted.

Thanks to the isolation of effects, it would be enough for companies to design the effects layers and outsource the development to anyone, even the best black-hat hackers, with the necessary knowledge, knowing that the code they receive will comply 100% with their initial design.

Talking about how to do things right and thus ensure data protection by design and by default.

Note: And the best thing is that you do not have to believe in my word, you just have to trust a piece of technology that is based on solid foundations of Mathematics and Computers Science

[Safe {H}askel]: (David Terei, David Mazières, Simon Marlow, Simon Peyton Jones) Haskell ’12: Proceedings of the Fifth ACM SIGPLAN Symposium on Haskell, Copenhagen, Denmark, ACM, 2012