# Proposal: Using modules like a record

In functional programming, it is typical to write patterns for calling functions, instead of just calling them. Example: `List.map`

``````map : (a -> b) -> List a -> List b
``````

Apply a function to every element of a list.

``````map sqrt [1,4,9] == [1,2,3]

map not [True,False,True] == [False,True,False]
``````

Here, instead of `[not True,not False,not True]` or `[sqrt 1,sqrt 4,sqrt 9]`, we use `map` to reuse the pattern of calling a function on each element of a list and putting the result in the new list. The key technique here is that we get to choose to use `not` or `sqrt` or `squareThenSubtractOneThenDivideByOneGreaterThanTheOriginalNumberThenAddOne` or what have you. `map` wasnâ€™t written to act on data directly but rather to shuffle numbers around in the right way according to some often-enough-used pattern. We decouple the generic use-case from the operation. This technique is really fascinating to functional programming nerds such as mâ€™self, because what is a really painful and stupid process in OOP is as easy and simple as adding another parameter in FP. This is one of the many reasons why I love Elm and think itâ€™s worth sharing!

Of course, thereâ€™s no reason to stop at `map`, we also have the functions `List.foldl`, `List.sortWith`, `Dict.merge`, `Maybe.andThen`, the `List` goes on (see what I did there?). The way to spot them is to find a function that takes at least these parameters:

1. A function, and
2. Some data you could call that function with

Notice something interesting. `List.map` takes one decoupled function, but `Dict.merge` takes three! How many can you have? Probably quite a few, but thereâ€™s no reason to have a tediously long type signature for data you reuse often enough to warrant the abstraction. Instead, we can use a record, which gives us the benefit of having named fields. For example, letâ€™s say we have a `Vector` module:

``````-- In the Vector module

type Vector
= Vector Float Float

add : Vector -> Vector -> Vector

negate : Vector -> Vector
negate = . . .

normalize : Vector -> Vector
normalize = . . .

-- In some other module where you use the Vector module

type alias VectorOps =
{ add : Vector -> Vector -> Vector
, negate : Vector -> Vector
, normalize : Vector -> Vector
}

vector : VectorOps
vector =
, negate = Vector.negate
, normalize = Vector.normalize
}
``````

So here I have three ops, `add`, `negate`, and `normalize`, but you can imagine that for some complex forms you could have `save`, `send`, `validate`, `review`, `share`, `autofill`, and whatever else your heart beckons for.

This is pretty powerful on its own; now you can use the `Vector` moduleâ€™s datastructure in patterns that take any combination of these vector ops. Consider `Addable`:

``````type alias Addable ops datastructure =
{ ops
| add : datastructure -> datastructure -> datastructure
}

doThingsToSomethingYouCanAdd : Addable ops ds -> ds -> ds -> otherStuff -> etc -> . . .
. . .
``````

By calling `doThingsToSomethingYouCanAdd vector myVectorA myVectorB otherstuff . . .`, you have decoupled a function that adds data. Congratulations! This function does â€śsomethingâ€ť with a datatype where it makes sense to add them together (as opposed to a datatype where it doesnâ€™t, such as `VectorAndScalar`, or `Fruit`, i.e. what is Apple + Orange?). Fill in â€śsomethingâ€ť with â€śsum a listâ€ť or â€śmuliplyâ€ť or whatever. Just as multiplication follows from addition, subtraction follows from having both `add` and `negate`. Iâ€™ll leave it to the reader to come up with how to write `AddableAndNegateable ops ds`â€“letâ€™s move on.

So this is the clincher: you need to fill in an `...Ops`-type record, like `VectorOps`, `PerlinNoiseMapOps`, `MassInNonEuclideanSpacetimeOps`, `FruitThatCanSomehowBeNegatedOps`, and so on for every datatype module that is `Addable` or `AddableAndNegatable` or `NegateableAndNormalizeable`, etc. And because youâ€™ve chosen to abstract it this far, there are probably quite a few such modules. This means doing this pattern again and again:

``````vector : VectorOps
vector =
, negate = Vector.negate
, normalize = Vector.normalize
}

perlinNoise : PerlinNoiseMapOps
perlinNoise =
{ negate = PerlinNoise.negate
, normalize = PerlinNoise.normalize
}

sillyMass : MassInNonEuclideanSpacetimeOps
sillyMass =
, normalize = MassInNonEuclideanSpacetime.normalize
}

fruit : FruitThatCanSomehowBeNegatedOps
fruit =
{ negate = Fruit.negateSomehowIdk
}
``````

This is tedious. Unfortunately, no pattern can solve this because it is a technique leveraging Elmâ€™s type system rather than performing some kind of computation. Finally, 5031 characters in, I get to my proposal: Elm could benefit from having syntactic sugar that writes these records for the user based on the module definition instead of them having to repetitiously do this manually. This makes writing Elm a more pleasant experience for this user.

To specify, say we have this module, likely centered around a datastructure:

``````module MyModule exposing (..)

. . .

functionA : a -> b -> c

. . .

functionB : d -> e -> f

. . .
``````

The only functions in `MyModule` are `functionA` and `functionB`. I propose some shortened syntax to write this:

``````mymodule =
{ functionA = MyModule.functionA
, functionB = MyModule.functionB
}
``````

adapted accordingly to however many functions `MyModule` has. In this way, we use a module like a record to solve the problem of many large, alike, decoupled datastructures. I believe @evancz and his team is capable of coming up with a nice design for this syntax, but just for the sake of being concrete Iâ€™ll show what I have in mind.

At the module import, there is a new keyword, `fitting`, that works quite like `exposing`.

``````import MyModule as M
fitting
( mymodule :
{ functionA : a -> b -> c
, functionB : d -> e -> f
}
)
exposing (foo, bar, baz, quux)
``````
• `mymodule`'s properties are â€śfitted toâ€ť `MyModule.functionA` and `MyModule.functionB` if theyâ€™re exposed from `MyModule`
• The type signature here is optional, but recommended: we wouldnâ€™t want the creator of `MyModule` to go and change its names and types on us without us knowing!
• I donâ€™t expect this to be used for many exposed modules, just internal ones. If any exposed modules do this, they probably aim for extensibility
• As of yet, I donâ€™t know of a practical use for the cases below. I hope commenters will tell me what they think of them!
• Fitting a type that isnâ€™t a record, like a `Float` or a union type. Though, I figure an extensible record could be useful
• Fitting multiple type declarations, with `mymoduleA : { . . . }`, `mymoduleB : { . . . }`, etc.

================================================================================
Footnote:

I get that Elmâ€™s general policy is to be meticulous when adding features because adding features adds more stuff new users need to learn to use Elm. Here, @luke has said that he evaluates features like so:

To what greater degree does the feature enable an Elm user to achieve the goals Elm proposes to help with beyond what is already possible?

In this interview @evancz has stated Elmâ€™s goal to â€śprovide a pleasant programming experience [â€¦] how can we make it possible to do that and for you to have fun doing it?â€ť I argue that this feature, while putting hardly any new mental burden on users, still promotes the fun, expressive, and collaborative tooling Elm aims for because of the following reasons:

• The parallel between records and modules is already demonstrated in the syntax by the namespacing (.) operator
• The reduced redundancy allows for datatypesâ€™ features to be implemented in linkable pieces like Lego bricks instead of case-by-case, making the code more concise and reducing naming inconsistencies
• Modules-as-records reduces gratuitous abstraction and focuses on the reuse that actually matters via the option to enforce the type signature

================================================================================

Thanks for reading! Let me know what you think of this idea.
anon5324703

2 Likes

I recommend looking into OCaml and Adga, which both do something like this.

I think the interesting aspect of these systems is that they require complexity in the type system that is quite surprising. In OCaml you can instantiate modules dynamically, even giving them arguments, so you can create different versions. But now, if a module creates a `type` how do we know it is the same as in other instantiations of the module? Should it be? Should it not? Creating new types dynamically also starts breaking the border between values and types. In Agda, a dependently typed language, this is less of a problem, but it does not seem â€śsimpleâ€ť to me at least.

Putting the type problems aside, I suspect it makes it harder to do per-function dead code elimination. Iâ€™m not 100% certain about that, but at the very least, it would complicate a bunch of optimizations.

So I think itâ€™s an idea that sounds cool. I have thought about things like this as well. Itâ€™s not just about syntax though. It creates some very hard problems. So I recommend looking for languages that have this and seeing what issues it causes.

3 Likes

Ur/Web is also a very interesting language to have a look at

http://www.impredicative.com/ur/tutorial/tlc.html

I guess the thing evancz is referring to is called a â€śFunctorâ€ť in the SML/OCaml land.

Yeah, its type system seems to resemble Haskell. Thanks for the recommendation! Iâ€™ll be sure to look into it further sometime soon.

Thatâ€™s interesting, it seems like this proposal inadvertently ran into problems shared with type class systems. Yikes!

You gave quite a few interesting points!

• I recommend looking into OCaml and Adga

Thanks for the recommendations! Iâ€™ll definitely read up on thoseâ€“I think Iâ€™ll use OCaml for one of my classes at some point. Looks really intriguing!

• if a module creates a `type` how do we know it is the same as in other instantiations of the module?

I donâ€™t quite understand what you mean by this, because modules donâ€™t create their own types in this system; e.g. `MyModule` doesnâ€™t create the type

``````{ functionA : a -> b -> c
, functionB : d -> e -> f
}
``````

and `PerlinNoise` doesnâ€™t create the type `PerlinNoiseMapOps`, and so on. These are defined by the user. I wouldnâ€™t quite classify this as â€ścreating new types dynamicallyâ€ť but rather â€śassigning values to a member of that type dynamically.â€ť Could you explain what you mean by that?

EDIT:
Hereâ€™s one thing that came to mind: Do you mean how do we know the `Vector` module can be fitted to member of the type `VectorOps`, `PerlinNoise` can be fitted to `PerlinNoiseMapOps`, etc.? Because a property of the record has the same name and a correct type. I donâ€™t have any math to prove that this system is 100% deterministic, but my intuition says so. But thereâ€™s no doubt that Iâ€™ll check out OCaml and Agda to see what caveats there are to this.

• I suspect it makes it harder to do per-function dead code elimination

I donâ€™t know if Elm has any level of â€śnested record dead code eliminationâ€ť, but if it did it would be quite complex indeed. This would certainly be a feature that requires some wisdom to implement, I donâ€™t know if that puts too much optimization burden on the Elm user or not. It might help to forego this:

The only functions in `MyModule` are `functionA` and `functionB`

so that you can still dead-code-eliminate the other functions you donâ€™t use (if you read closely, you see that `MyModule` also has the functions `foo`, `bar`, `baz`, and `quux`. That was an error on my part, but not anymore!). Either way, this is practice is not for every function of every module, thatâ€™s for sure.

• So I think itâ€™s an idea that sounds cool.

Thanks! I donâ€™t know why, but Elm above all other languages invokes my sensibilities as a language designer. There are clearly a lot of difficult concepts to juggle when making these things such as those you mentioned, and I think you and your team are doing a great job! Keep up the good work!

I think the thing you are describing is â€śfirst-class modulesâ€ť which is like parameterizing a module by another module.

I think this page talks about modules in Agda in a way that may be a decent preview. The part about â€śParameterised modulesâ€ť is where you start creating things dynamically.

If you want to get deep into details, I know that F-ing Modules is a commonly recommended paper on the topic.

I also want to emphasize that in language design, a very small percentage of ideas that sound cool actually end up working well when you get into all the details. So I think I make a distinction in my mind about sounds good vs is good when it comes to language design, because neither implies the other unfortunately!

For sure! Thanks for the resources!

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.