1. 7
  1. 4

    An “object”-like representation allows for a vast amount of flexibility. Because a consumer of an OIntSet can use any value as long as the value has provided implementations of the relevant “methods”, we can easily and conveniently define new instances of OIntSet that have radically different internal representations.

    Objects are one way of doing this but we can actually do this in a more functional manner by using free monads and GADTs. This allows you to depend on the “interface” (your GADT) and swap out your implementation by running it through a different interpreter. This approach also allows you to track effects more granularly.

    You can check out libraries like polysemy to see this in action.

    Here’s an example of what I’m talking about (I’ve simplified an an example from the polysemy docs). In this example we can replace teletypeToIO with another completely different interpreter to get different implementations.

    data Teletype m a where
      ReadTTY  :: Teletype m String
      WriteTTY :: String -> Teletype m ()
    
    teletypeToIO = interpret \case
      ReadTTY      -> embed getLine
      WriteTTY msg -> embed $ putStrLn msg
    
    echo = do
      i <- readTTY
      case i of
        "" -> pure ()
        _  -> writeTTY i >> echo
    
    main :: IO ()
    main = runM . teletypeToIO $ echo