Search code examples
haskellhaskell-lens

How to fix compilation re haskell rigid type variable


I have a an aggregate type named Step and from it, some concrete types are created:

class (Eq q, User a, User b, User c) => (Step q a b c) where
  {-# MINIMAL performerA, performerB, performerC, completionDate  #-}
  performerA           :: Lens' q a
  performerB           :: Lens' q b
  performerC           :: Lens' q (Maybe c)

data Step1
makeLenses ''Step1
instance Step Step1 UserType1 UserType2 UserType3 where
  ...

data Step2
makeLenses ''Step2
instance Step Step2 UserType1 UserType2 UserType4 where
  ...

My factory function takes some arguments and returns a step corresponding to some criteria expressed using case:

mkStep :: (User a, User b, User c, Step q b a c) 
       => Package a b
       -> b 
       -> Maybe c
       -> Either ValidationError q
mkStep package performer' nextPerformer' = do
  return step
  where
    step = case (package ^. currentPerformer . memberType, performer' ^. memberType, nextPerformer' ^. memberType) of 
            (MemberType3, MemberType2, MemberType4) 
              -> Step1 performer' (package ^. currentPerformer) nextPerformer'
            (MemberType3, MemberType2, MemberType1) 
              -> Step2 performer' (package ^. currentPerformer) nextPerformer'

Some extra info on other types involved in constructing a Step:

data MemberType = MemberType1 
                | MemberType2
                | MemberType3
                | MemberType4
                deriving (Show, Eq)

class (Eq a, Show a) => User a where
  {-# MINIMAL memberType, userId #-}
  memberType  :: Lens' a MemberType
  userId      :: Lens' a MemberId

data UserType1
makeLenses ''UserType1
instance User UserType1 where
  ...
data UserType2
makeLenses ''UserType2
instance User UserType2 where
  ...
data UserType3
makeLenses ''UserType3
instance User UserType3 where
  ...
data UserType4
makeLenses ''UserType4
instance User UserType4 where
  ...

data Package a b = Package
    { _currentPerformer       :: a
    , _nextPerformer          :: Maybe b
    } deriving (Show, Eq)
makeLenses ''Package

However, when I attempt building the code, I get the following:

> stack build
Building all executables for 'ss-model-ddd' once. After a successful build of all of them, only specified executables will be rebuilt.
ss-model-ddd> build (lib + exe)
Preprocessing library for ss-model-ddd-0.1.0.0..
Building library for ss-model-ddd-0.1.0.0..
[7 of 7] Compiling Steps

/home/aoaddeola/ss-model-ddd/src/Steps.hs:103:10: error:
    • Couldn't match expected type ‘q’
                  with actual type ‘Step2’
      ‘q’ is a rigid type variable bound by
        the type signature for:
          mkStep :: forall a b c q.
                    (User a, User b, User c, Step q b a c) =>
                    Package a b -> b -> Maybe c -> Either ValidationError q
        at src/Steps.hs:(96,1)-(100,34)
    • In the first argument of ‘return’, namely ‘step’
      In a stmt of a 'do' block: return step
      In the expression: do return step
    • Relevant bindings include
        mkStep :: Package a b
                  -> b -> Maybe c -> Either ValidationError q
          (bound at src/Steps.hs:101:1)
    |
103 |   return step
    |          ^^^^

/home/aoaddeola/ss-model-ddd/src/Steps.hs:109:18: error:
    • Couldn't match type ‘Step1’
                     with ‘Step2’
      Expected: Step2
        Actual: Step1
    • In the expression:
        Step1
          performer' (package ^. currentPerformer) nextPerformer'
      In a case alternative:
          (MemberType3, MemberType2, MemberType4)
            -> Step1
                 performer' (package ^. currentPerformer) nextPerformer'
      In the expression:
        case
            (package ^. currentPerformer . memberType, performer' ^. memberType, 
             nextPerformer' ^. memberType)
        of
          (MemberType3, MemberType2, MemberType1)
            -> Step2
                 performer' (package ^. currentPerformer) nextPerformer'
          (MemberType3, MemberType2, MemberType4)
            -> Step1
                 performer' (package ^. currentPerformer) nextPerformer'
    |
109 |               -> Step1 performer' (package ^. currentPerformer) nextPerformer'
    |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Finding it difficult to figure out what the problem could be. My first culprit is the fact that I'm using Lenses, which are only available at runtime (I guess).

Any solution to the issue will be appreciated.


Solution

  • This doesn't really have anything to do with lenses. You've written a mkStep function that's a lot like:

    mkFoo :: (Show q) => Bool -> q
    mkFoo b = case b of True -> 'c'
                        False -> ()
    

    In other words, you've claimed mkStep will provide whatever (rigid AKA user-specified) type Show q => q the caller requests, when in fact you're going to provide a q determined by your function, not the caller, with a type that's based on a runtime input at that.

    This is broken in two ways. You can't claim to provide any q the caller wants and then provide only a specific q (e.g., Step2), and you can't write a case statement that evaluates to expressions of two different types (Step1 versus Step2) depending on a scrutinee of a single type (MemberType, MemberType, Membertype).