Row types and modules
=====================

April 01, 2023

I was thinking about modules a few years ago. One of the directions was the
intersection of modules with row types. I decided to write something down to
keep the idea.

OCaml consists of two languages:

- term language — to construct programs with terms and types;
- module language — to create signatures, modules, and functors.

1ML merges these levels — modules become values. I noticed that modules behave
in a similar way/correspond to records. Since the Service/Handle pattern in
Haskell is about using a record to represent an interface and to have different
implementations by filling the record's fields, what if we use row types on
module level? If row types might be seen as an alternative to records, then it
might be possible to represent row types values on module level as an
alternative to modules.

| Term language |  | Module language    |
|---------------|--|--------------------|
| Records       |~>| Modules            |
| Row types     |~>| ?                  |

I don't want to imagine an artificial language for this purpose and will use
Haskell with the Handle pattern, extending the previous post.

import Data.Row

import qualified WindProvider
import qualified TemperatureProvider

type Location = String
type Day = String
type WeatherData = String

type Methods = ("getWeatherData" .== (Location -> Day -> IO WeatherData))
  .+ WindProvider.Methods
  .+ TemperatureProvider.Methods

-- We can remove an item from the record
type WindAndTemperatureMethods = Methods .- "getWeatherData"

test1 :: Rec WindAndTemperatureMethods
      -> Rec (WindProvider.Methods .+ TemperatureProvider.Methods)
test1 = id

-- It's possible to override methods
type MethodsWithUpdatedWeatherData =
  ("getWeatherData" .== (Location -> Day -> IO Int)) .// Methods

test2 :: Rec MethodsWithUpdatedWeatherData
      -> Rec (
          ("getWeatherData" .== (Location -> Day -> IO Int))
          .+ WindProvider.Methods .+ TemperatureProvider.Methods
        )
test2 = id

-- And to remove them by taking a row difference
type MethodsWithoutWeatherData =
  Methods .\\ ("getWeatherData" .== (Location -> Day -> IO WeatherData))

test3 :: Rec MethodsWithoutWeatherData -> Rec WindAndTemperatureMethods
test3 = id

-- The minimum join of the two rows.
type JoinedMethods = Methods .\/ Methods

test4 :: Rec JoinedMethods -> Rec Methods
test4 = id

That's similar to my previous approach with vinyl library — `Methods` is just
a union of other methods with its own `getWeatherData` extension. The
difference is that vinyl record supports only appending new elements while with
rows it's also possible to remove items, take a difference, do a minimum join,
override rows, etc.

By using row types as signatures we get more flexible interfaces, more ways to
express and combine them. But I don't remember if I ever had a motivation for
this in a real world.