tweag/capability

Relationship between tags and record field names

aherrmann opened this issue · 3 comments

In the current implementation the instance tag, i.e. tag in HasState tag s m, is unrelated to any potential record field labels in the state type. E.g. the following instances (MonadState, and Field)

instance State.MonadState s m => HasState tag s (MonadState m)
instance ( Generic s', Generic.HasField' field s' s, HasState tag s' m )
  => HasState tag s (Field field m)

exist for any tag.

Consider the following example.

data MyState = MyState { msFoo :: Int }
  deriving Generic

newtype MyStateM a = MyStateM (State MyState a)
  deriving (Functor, Applicative, Monad)
  deriving (HasState "foo" Int) via
    Field "msFoo" (MonadState (State MyState))

The field label msFoo and the tag foo are unrelated.

Alternatively, we could enforce that the tag and record field match. For nested records, or other situations where this may not be the case, we could introduce a Rename combinator. The above example could then look like this.

newtype MyStateM' a = MyStateM' (State MyState a)
  deriving (Functor, Applicative, Monad)
  deriving (HasState "foo" Int) via
    Rename "foo" (Field "msFoo" (MonadState (State MyState)))

where MonadState would still exist for any tag, but Field requires that tag and field match.

As a side note, for non-records a Position combinator could be introduced that uses HasPosition' from generic-lens.

See #7 (comment)

cc @aspiwack

Thanks for this issue.

As a side note, for non-records a Position combinator could be introduced that uses HasPosition' from generic-lens.

This sounds quite reasonable, indeed.

Both options seem to have their inconveniences. I think naming the capability after the field is less surprising. It improves composability somehow, as per the comment linked in the issue, it's not clear that it's a useful improvement.

But having the tag be arbitrary is less verbose. For instance, we would need, to be able to write the instance for Field to add an extra argument in Field for the tag of the capability we're modifying.

I don't have a conclusion. It needs to be pondered carefully.

I don't have a conclusion. It needs to be pondered carefully.

I agree, #12 explores a possible direction to implement this.

For instance, we would need, to be able to write the instance for Field to add an extra argument in Field for the tag of the capability we're modifying.

At least in #12 that doesn't seem to be the case. However, due to the functional dependency on HasState tag s m, namely tag m -> s, it seems that we need a second argument on Rename, namely Rename newtag oldtag.