Haskell lens: let binding of Traversal'

132 views Asked by At

I'm a bit confused and don't know where to look for the information/explanation of the following "issue" (it's not an issue per se, but more of a situation where I don't understand what is wrong behind the scenes):

I have a monad transformer stack with StateT. At some point in my function I would like to bind a small fraction of my state into the local variable so I can refer to it instead of writing out the whole path to the chunk of the state I'm interested in. Here is what I mean:

{-# LANGUAGE ScopedTypeVariables #-}

...

someFunction :: MyMonad ()
someFunction = do
  ...
  let x :: Traversal' MyState MyDataT = myState.clients.ix clientIdx.someData.ix dataIdx
  ...

Now this doesn't compile:

Couldn't match type ‘(MyDataT -> f0 MyDataT)
                     -> MyState -> f0 MyState’
              with ‘forall (f :: * -> *).
                    Control.Applicative.Applicative f =>
                    (MyDataT -> f MyDataT) -> MyState -> f MyState’

But if I move the referencing of this data chunk into a function then everything compiles ok:

someFunction :: MyMonad ()
someFunction = do
  ...
  let x = clientData clientIdx dataIdx
  ...

  where clientData :: Int -> Int -> Traversal' MyState MyDataT
        clientData clientIdx dataIdx = myState.clients.ix clientIdx.someData.ix dataIdx

I'm looking for some kind of information that will help me understand what is going on here, why it happens, so that I'm aware of what I'm doing wrong. Basically I would like to expand my knowledge to understand this use case a bit better.

1

There are 1 answers

0
András Kovács On BEST ANSWER

The key point here is that the annotation should be in a separate line. If we do that, then we have a binding with an explicit type, as far as GHC is concerned.

someFunction :: MyMonad ()
someFunction = do
  ...
  let x :: Traversal' MyState MyDataT 
      x = myState.clients.ix clientIdx.someData.ix dataIdx
  ...

What you first tried very rarely works as you intended:

let x :: Traversal' MyState MyDataT = ...

This is a binding without an explicit type; the annotation is inside the left hand side. GHC considers the type of the variable fixed before looking at the right hand side, but the annotation only applies to the left hand side, so GHC just infers a type for the right had side separately, and then tries to match it exactly with the annotation. This makes the type checking fail for all but the simplest non-polymorphic cases.

The right way of putting annotations inside bindings is the following:

let x = ... :: Traversal' MyState MyDataT

Here, GHC first assigns a "malleable" indeterminate type variable to x, then infers a type for the right side informed by the annotation there, then unifies the type of x with it.

This is still a binding without an explicit type, but it works in general if we enable NoMonomorphismRestriction, for reasons detailed in this SO question.