This the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Idioms

Reasons for various idioms in the codebase

The document to see the rational for any of our idioms

Qualified Imports

Throughout the codebase every import except the standard library is imported qualified. This allows us the flexibility of having common naming patterns between modules.

Some of the more obvious ones are

  • T

  • op

  • of<Type>

  • to<Type>

  • etc.

Further we often import our modules with long names, like Namesymbol for Juvix.Library.NameSymbol.

Consider the following example if we have short qualification names.

  import qualified Juvix.Library.NameSymbol as N


  ... 100+ lines of logic ...


  foo :: N.T -> Symbol -> Symbol
  foo ns symb = N.toSymbol <> symb


  ... more lines of logic ...

In complicated logic where we have over a dozen imports, it's quite taxing to see N.toSymbol, as what is this N? However it becomes very clear if we saw NameSymbol.toSymbol

  import qualified Juvix.Library.NameSymbol as NameSymbol


  ... 100+ lines of logic ...


  foo :: NameSymbol.T -> Symbol -> Symbol
  foo ns symb = NameSymbol.toSymbol <> symb


  ... more lines of logic ...

Further it makes our standard idioms of to<Type> (toSymbol) become a bit harder to reason about, what is going to Symbol in this instance? We'd be forced to have names like N.NameSymboltoSymbol, essentially removing any standardization of module's and ensuring that we import everything unqualified.

Module.T

Most modules contain a type T within them. For example we have NameSymbol.T. The T represents the quintessential type of the module. The decision is a consequence of having long qualified names for each module imported.

Consider the following scenario where we did not make this choice, and instead had the full name.

  import qualified Juvix.Library.NameSymbol as Namesymbol


  ... 100+ lines of logic ...


  foo :: NameSymbol.NameSymbol -> Symbol -> Symbol
  foo ns symb = NameSymbol.toSymbol <> symb


  ... more lines of logic ...

Here we are quite tempted to import the module as unqualified for this one type, as it's obvious what we mean! Further, it might encourage short import names like N which we discuss in the previous section.

The name T over Ty or any other variant comes from OCaml where they name all their types T. This in OCaml has practical benefits for their module functors which expect the module to have some principled type T to be generic over. This has the side effect that principled functions and types are named consistently throughout modules, which helps us get more of an intuition about the code without having to ask what is the point of any particular module.

Capability

Capability is our replacement for MTL in the codebase. See the article linked below for a detailed explanation of the benefits over MTL.

More can be read in our sub article below

1 - Capability

Further expanding upon why we chose capability

Usage of the Capability Library

  • Historically Haskell has been written with the mtl library. This library allows Monads to stack on top of each other in a linear fashion. This is very nice, as it allows the pluming of how each layer interacts to be abstracted away. However these advancements do not come without costs which are outlined below

    • Downsides with MTL

      • ReaderT design pattern.

        • This means that as our monad stacks grow and our state monad fails to deal with concurrent programming, we will have to rewrite the backend to use IO over a single Reader Monad.

      • Explicit heavy use of lenses

        • Due to every state being forced into a single reader monad, and not wanting to couple every function with everything in the reader environment, lenses with views into the struct itself must be used instead.

      • Inefficiencies in execution

        • Due to MTL Working as a list of effects rather than a set of effects, the more layers one adds to one's MTL stack, the slower the execution speed is.

        • Even worse, is that the writer monad always leaks space and thus should be avoided

  • Solving These Issues with the Capability library

    • Fundamental ideas

      • Instead of having a list of monads one linearly stacks, we instead have a `set` of effects. This set may contain that a database connection is read-only or that this shared thread resource is readable and writable.

      • The set of effects is in reality, a single monad, with a view into the structure itself. So, a read-only view mimics the reader monad, while a write-only view mimics the writer monad.

      • This allusion is achieved by using a new feature in GHC 8.6 known as deriving via, which allows us to derive the proper interface and `effect` for each field in our environment.

    • ReaderT design pattern

      • This issue goes away, as the "State" effect could be implemented as either a proper state monad (we currently do this in the source code) or as a reader monad. This choice of how some computation gets computed is separated from the function logic itself.

    • Explicit heavy use of lenses

      • This issue goes away, as the deriving via extension derives the correct interfaces for us in a straightforward way

        • Currently it is a bit tedious, but it's obvious in how tedious it is, thankfully.

    • Inefficiencies in execution

      • GHC thankfully knows how to optimize a single layer monad like Reader or State very well, so performance should be great.

Further Improvements

Currently the capability library only displays each field of a record as an effect. Ideally we'd have effects explain certain kinds of computation instead, and logically bundle these fields into some kind of greater effect.

Tomas has brought to my attention of the eff library which may some day replace or aid our usage of capability.