top | item 21041678

(no title)

sjakobi | 6 years ago

What are modular implicits?

discuss

order

TheAsprngHacker|6 years ago

The ML programming language is known for its advanced module system. First, you have structured, which are your traditional idea of a module. Structures define types and data. Signatures describe the interface of a module and are like the module's "type." By making types abstract or not mentioning members, signatures achieve encapsulation of modules. Finally, you have functors, which are functions from modules to modules. The parameter is constrained by a signature, and the client can pass any module that conforms to the signature to get an output module that uses the passed module in its implementation.

The module system is used when a type must support certain operations. For example, the Set module (https://caml.inria.fr/pub/docs/manual-ocaml/libref/Set.html) defines a signature called OrderedType for types equipped with a comparison function, and any module that implements OrderedType can be used to make a set.

Notably, ML modules differ from Haskell typeclasses because more than one module can exist for the same combination of types. However, you must pass ML modules explicitly, whereas typeclass instances are passed implicitly. So, ML modules have more power than typeclasses, but at the cost of convenience.

Here is an example where modules are used to make a set:

    module LtInt = struct
      type t = int
      (* Use built-in polymorphic comparison *)
      let compare = compare
    end

    module GtInt = struct
      type t = int
      let compare lhs rhs = compare rhs lhs
    end

    module LtIntSet = Set.Make(LtInt)

    module GtIntSet = Set.Make(GtInt)
However, modules can quickly get inconvenient:

    module type Monoid = sig
      type t
      val op : t -> t -> t
      val e : t
    end

    module Addition = struct
      type t = int
      let op = (+)
      let e = 0
    end

    module Multiplication = struct
      type t = int
      let op = ( * )
      let e = 1
    end

    module FoldLeft (M : Monoid) = struct
      let fold_left = List.fold_left M.op M.e
    end
Here, it's probably less verbose to just pass the operation and starting value directly to List.fold_left than to use modules! Debatably, this is as equally inconvenient as Haskell's newtype solution to multiple instances, but if there is only one instance, typeclasses are much cleaner to use.

Modular implicits are a long-awaited feature that will allow ML modules to be passed implicitly.

https://www.cl.cam.ac.uk/~jdy22/papers/modular-implicits.pdf