Previously, we saw that we could "interpret" the
Free monad into another monad, namely,
Effect, to simulate state manipulation effects. This idea is similar to a compiler: a high-level language (i.e.
Free's languages) get "compiled"/"interpreted" into a lower-level language (i.e.
Aff). However, what if we recursively interpreted the
Free monad into another
Free monad for a few rounds until the last one gets interpreted into the
type Free1 = Free f a type Free2 = Free g a type Free3 = Free h a -- assuming there is a natural transformation from one to another... Free1 ~> Free2 Free2 ~> Free3 Free3 ~> Effect -- means we can effectively write this Free1 ~> Effect
This allows us to write one high-level language that gets "interpreted" into a lower-level language, which itself gets interpreted into an even lower-level language. Each
~> is going from a high-level abstract language to a lower-level more-platform-specific language. In other words, a chain of compiler, where the output of the previous is the input of the next. Updating our code above to use meta-language, we would have something like this:
-- Let your domain experts write their domain-specific "programs" -- using a familiar domain-specific language... type HighestLevelLanguage = CoproductN Language1 Language2 Language3 -- ... type HighestLevelProgram = Free HighestLevelLanguage type HighLevelLanguage = CoproductN LanguageA LanguageB LanguageC -- ... type HighLevelProgram = Free HighLevelLanguage type FirstCompiler = HighestLevelProgram ~> HighLevelProgram -- and let your technical experts "translate" them into working programs -- via NaturalTransformations type LowLevelLanguage = CoproductN LanguageX LangaugeY LanguageZ -- ... type LowLevelProgram = Free LowLevelLanguage type SecondCompiler = HighLevelProgram ~> LowLevelProgram type ThirdCompiler = LowLevelProgram ~> Effect -- or Aff -- given this... HighestLevelProgram ~> HighLevelProgram HighLevelProgram ~> LowLevelProgram LowLevelProgram ~> Effect -- or Aff -- we compose them to get this type RealCompiler = HighestLevelProgram ~> Effect -- or Aff -- which enables this... runProgram :: RealCompiler -> HighestLevelProgram e -> Effect e -- ... a program written by a domain-expert in a domain-specific -- language who is ignorant of all the technical and platform-specific -- details that make it work... -- -- ... that has been optimized and works for numerous backends by -- your technical experts. -- -- Similar to the ReaderT design pattern, we can always change -- the infrastructure code to use a new framework, UI, database, etc. -- without rewriting any of the domain-specific code.
~> acts like a compiler that compiles the higher-level language (input) into a lower-level language (output), one can also "optimize" a compiler's output in some cases via
FreeAp, the Free Applicative type. Unfortunately, this is not covered here (yet), but one should be aware of it. John De Goes overviews this idea below.
To see some examples and the implications of this idea, read the following links and translate the
IO monad to
Effect and the mention of Purescript's now-outdated
Eff monad to
Effect. Also note that
MTL works faster than
Free on Haskell, but I don't know their performance comparison on Purescript: