Is it possible to distinguish on a type level between a -> f b and a -> b -> f c where f is a Functor?

166 Views Asked by At

I'm trying to build a way to transform functions of type (with any number of arguments):

Monad f => env -> f z
Monad f => env -> a -> f z
Monad f => env -> a -> b -> f z

into:

MonadReader env m => m z
MonadReader env m => a -> m z
MonadReader env m => a -> b -> m z

So I've created the following class (with helper closed type families):

{-# LANGUAGE AllowAmbiguousTypes        #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE FunctionalDependencies     #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures             #-}
{-# LANGUAGE RecordWildCards            #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE TypeApplications           #-}
{-# LANGUAGE TypeFamilies               #-}

import           Control.Monad.Reader
import           Data.Kind

(&>) :: Functor f => f (p -> b) -> p -> f b
ff &> x = ff <&> ($ x)
infixl 4 &>

-- Environment type put into the `MonadReader`
type family RdType a where
  RdType (r -> a) = r

-- Function type after chopping off first argument. It distinguishes every case, 
-- because we need access to `m` type.
type family RetType a where
  RetType (r -> a -> b -> c -> m d) = a -> b -> c -> m d
  RetType (r -> a -> b -> m c) = a -> b -> m c
  RetType (r -> a -> m b) = a -> m b
  RetType (r -> m a) = m a

-- Class trying to capture functions with any number of arguments
class (MonadReader (RdType a) m, RetType a ~ b) => Unwrap a b m | a b -> m where
  unwrap :: a -> b

-- INSTANCE #1
-- instance (RetType (r -> a -> m b) ~ (a -> m b), MonadReader r m) => Unwrap (r -> a -> m b) (a -> m b) m where
--   unwrap f a = join $ asks f &> a

-- INSTANCE #2
instance (RetType (r -> a -> b -> m c) ~ (a -> b -> m c), MonadReader r m) => Unwrap (r -> a -> b -> m c) (a -> b -> m c) m where
  unwrap f a b = join $ asks f &> a &> b

However when I uncomment instance #1 and try to use it it results in an error like below:

data SomeEnv
f1 ::  Functor m => SomeEnv -> Int -> String -> m ()
f1 = undefined

-- uncommenting INSTANCE #1 results in error here
--
-- • Couldn't match type ‘[Char]’ with ‘SomeEnv’
--     arising from a functional dependency between:
--       constraint ‘MonadReader SomeEnv ((->) String)’
--         arising from a use of ‘unwrap’
--       instance ‘MonadReader r ((->) r)’ at <no location info>
-- • In the expression: unwrap f1 1 "asdf"
--   In an equation for ‘x’: x = unwrap f1 1 "asdf"
x :: MonadReader SomeEnv m => m ()
x = unwrap f1 1 "asdf"

It appears that instance #1 with RetType _ ~ (a -> m b) captures also a -> b -> m c case (covered by instance #2), which breaks everything.

An example for more context how unwrap can be used:

data Env m = Env { logger :: Db m }
data Logger m = Logger { flush :: m (), logAt :: Int -> String -> m () }

type AppEnv = Env App
newtype App a = App { unApp :: ReaderT AppEnv IO a }
  deriving (Functor, Applicative, Monad, MonadIO, MonadReader AppEnv)

myApp :: App ()
myApp = do
  logInfo "fooo"
  unwrap (flush . logger)

logInfo :: MonadReader AppEnv m => String -> m ()
logInfo txt = (logAt . logger) `unwrap` 1 txt

Is it possible to distinguish those two cases? Is it even possible to create such class?

0

There are 0 best solutions below