Skip to content

structural types: don't allow multiple constructors of the same type #3615

@ceedubs

Description

@ceedubs

This is relevant to both structural types and structural abilities.

If two constructors have the same type signature then either their name or ordering is going to be important, so the type that contains them really isn't structural in nature.

Consider the following example, where two people have defined a structural logging ability but have put the constructors in the opposite order:

structural ability Log a where
  debug : a -> ()
  error : a -> ()

structural ability Logger a where
  error : a -> ()
  debug : a -> ()

Adding them and using Names Log shows that both Log and Logger do indeed have the same hash:

names Log
 
  Type
  Hash:  #55tq7r3lqg
  Names: Log Logger

We can write a handler that simply shows log messages along with their logging level:

Log.levels! : '{Log a} r -> [(Text, a)]
Log.levels! =
  go acc = cases
    { x } -> acc
    { Log.debug a -> k } ->
      handle !k with go (acc :+ ("debug", a))
    { Log.error a -> k } ->
      handle !k with go (acc :+ ("error", a))
  thunk -> handle !thunk with go []

Since the Log and Logger abilities share the same hash, we can call Log.levels! with either constructor. But the results will vary, because their constructors are swapped.

> Log.levels! do
  Log.debug "debug"
  Log.error "error"
           ⧩
           [("debug", "debug"), ("error", "error")]
> Log.levels! do
  Logger.debug "debug"
  Logger.error "error"
             ⧩
           [("error", "debug"), ("debug", "error")]

Allowing this seems like a footgun. I think that the simplest solution is to make a type/ability fail to compiler if it has two constructors that aren't distinguishable by type.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions