Gregory Higley
Gregory Higley

Reputation: 16598

Why does unsafePartial not work with a simple functor in PureScript?

Unless I've made some simple error, the following pieces of code should be functionally identical:

-- This code does not compile
pg :: forall a. PG a -> Route a
pg sql = connect $ apply (runPG sql) (unsafePartial <<< fromJust <$> getConnection)

-- This code does not compile
pg sql = connect $ do
    connection <- unsafePartial <<< fromJust <$> getConnection
    runPG sql connection

-- This code does work
pg sql = connect $ do
   connection <- getConnection
   runPG sql $ unsafePartial $ fromJust connection

To help understand this, here are the relevant types:

-- Route has a MonadAff instance and all the usual stuff
newtype Route a = Route (ReaderT RouteState Aff a)

-- `connect` makes a connection to Postgres and injects it into the environment.
connect :: forall a. Route a -> Route a 

getConnection :: Route (Maybe Connection)

-- PG has a MonadAff instance and all the usual stuff
newtype PG a = PG (ReaderT Connection Aff a)

runPG :: forall m a. MonadAff m => PG a -> Connection -> m a

Here is the error:

Error found:
    in module AWS.Lambda.Router
    at src/AWS/Lambda/Router.purs:176:70 - 176:83 (line 176, column 70 - line 176, column 83)

      Could not match constrained type

        Partial => t1

      with type

        { client :: Client
        , pool :: Pool    
        }                 


    while trying to match type { client :: Client
                               , pool :: Pool    
                               }                 
      with type Partial => t1
    while checking that expression getConnection
      has type t0 (Maybe (Partial => t1))
    in value declaration pg

    where t0 is an unknown type
          t1 is an unknown type

    See https://github.com/purescript/documentation/blob/master/errors/ConstrainedTypeUnified.md for more information,
    or to contribute content related to this error.


    spago: Failed to build.

I think one of two things is going on here. Either I'm making some dumb syntax error or I don't understand Partial as I thought I did, although it seems pretty straightforward.

Upvotes: 2

Views: 125

Answers (1)

Fyodor Soikin
Fyodor Soikin

Reputation: 80805

This happens because type inference doesn't work very well with constraints. It doesn't always know if it needs to move constraints to the top or leave them in place. In general this is an undecidable problem, the compiler just tries to make the best effort.

Try this in the REPL:

> :t fromJust
forall a. Partial => Maybe a -> a

> :t unsafePartial           
forall a. (Partial => a) -> a

> :t unsafePartial <<< fromJust                  
forall t2. Partial => Maybe (Partial => t2) -> t2

See what happened? The function composition has "distributed" the Partial constraint over the parts of its argument, because it's not clear whether the constraint in fromJust applies to the whole type or individually to a. This might actually be a compiler bug, but it's hard to say for me right now.

So if you try to apply this function composition via <$> to getConnection, the compiler expects getConnection to have this weird doubly-nested type, which it doesn't.

Interestingly, you can use unsafePartial to remove the Partial constraint from the whole function fromJust, rather than just its return value:

> :t unsafePartial fromJust
forall t4. Maybe t4 -> t4    

This means that you can do something like this:

pg :: forall a. PG a -> Route a
pg sql = connect $ bind (unsafePartial fromJust <$> getConnection) (runPG sql)

Upvotes: 2

Related Questions