04-Run.purs

module Examples.HelloWorld.Run where

import Prelude
import Effect (Effect)
import Effect.Console as Console
import Type.Row (type (+))
import Type.Proxy (Proxy(..))
import Data.Functor.Variant (on)
import Run (Run, lift, interpret, case_)

-----------------------------------------
-- Core: Define any domain-specific concepts and their rules/relationships to
--         other domain-specific concepts

-- since there are no domain concepts or rules/relationships,
--    we won't include anything here...

-----------------------------------------
-- Domain: define business logic and capabilities need to run it:

-- - define our business logic as one pure function (program)
--      that uses data types that have Functor instances
--      to define the effects our program requires to be run
program :: forall r.
           Run                     -- A program that, given an interpreter that...
            ( LOG_TO_SCREEN        --    can enable logging to the screen
            + r                    --    and any other effects/capabilities
            )                      --       we may add later
            Unit                   -- ... will produce no output.
                                   -- However, running it will produce side-effects
                                   -- that make this program useful
program = do
  -- use capability to log a message to the console
  logToScreen "Hello World!"

-- - declare what the capabilities/effects are as data types with
--     derived instances for `Functor`,
--   define their type-level Strings that act as their corresponding label in a row,
--   define a type alias that makes using the data type in rows easier
--   and write their smart constructors.
data LogToScreen a = LogToScreen String a
derive instance Functor LogToScreen

_logToScreen :: Proxy "logToScreen"
_logToScreen = Proxy

type LOG_TO_SCREEN r = (logToScreen :: LogToScreen | r)

logToScreen :: forall r. String -> Run (LOG_TO_SCREEN + r) Unit
logToScreen msg = lift _logToScreen $ LogToScreen msg unit

-----------------------------------------
-- API: define how the pure domain concepts and logic above translate
--        into pure effects and impure effects via
--        "data type interpreters" (i.e. F-Algebras)

-- - an "interpreter" for each data type used above
logToScreenToEffect :: LogToScreen ~> Effect
logToScreenToEffect (LogToScreen msg next) = do
  Console.log msg
  pure next

-- Reader does not need to be done since `purescript-run` already defines
-- an interpreter via `runReader`

-- - an interpreter from the (Run <AllEffects>) type to the base monad, Effect.
--   Since we are not adding, any new effects/capabilities at this time,
--      we close the row kind using an empty row (i.e. "()")
--   Since we are using "open" row kinds, we need to use "case_" to insure that
--      all effects/capabilities are interpreted.
runProgram :: Run ( LOG_TO_SCREEN
                  + () -- closes the "open" row kinds of "program"
                  )
              ~> Effect
runProgram p =
  p
    -- use "interpret" and "case_" for capabilities
    # interpret (
        case_
          # on _logToScreen logToScreenToEffect
      )

-----------------------------------------
-- Infrastructure: any other code (i.e. databases, frameworks, libraries)
--                   that provides effects that do not appear in `Effect.*` modules

-- We aren't using other libraries (Node.ReadLine, Halogen, etc.).
-- Thus, nothing needs to go here for right now

-----------------------------------------
-- Machine Code: set up everything we need and then run the program

main :: Effect Unit
main = do
  -- no global config to set up here

  -- run the program by passing in the final data type
  -- that includes all the instructions to run via their interpreters.
  runProgram program