-
Notifications
You must be signed in to change notification settings - Fork 291
Description
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.