Write Genric function that takes function signature of varying length of argument as argument in type level

80 Views Asked by At

I want to create a function, that takes function as varying argument, the only thing i know about the funtion argument(which is a function itself) is they are wrapped in the same Monad....

myFunc :: forall arguments output m. 
                      MonadEffect m 
                   => (arguments -> m output)
                   ->  m (argument -> m output)
myFunc fn = do
            -- getting function's name using FFI and other stuff...
            pure fn 

I'm able to write the function, when I explicitly specify the argument length like,

-- for function with ONE argument
myFunc1 :: forall argument1 output m
                   . MonadEffect m 
                   => (argument1 -> m output)
                   -> m (argument1 -> m output)
myFunc1 fn = do 
              -- do something 
              pure fn


-- for function with TWO argument
myFunc2 :: forall argument1 argument2 output m. 
                   MonadEffect m => 
                   (argument1 -> argument2 -> m output) ->
                   m (argument1 -> argument2 ->  m output)
myFunc2 fn = do 
             -- do something 
             pure fn

How can I write argument1 -> argument2 -> m output as argument -> m output in type level?

is there any type/Constraint that helps in doing so?

ps: I just want to understand whether it's possible

1

There are 1 best solutions below

1
K. A. Buhr On

In Haskell, you can define type families that calculate the return type of a function:

type family Result f where
  Result (a -> b) = Result b
  Result b = b

and a type-level list of the types of its arguments (in reverse order):

type Arguments f = Arguments' '[] f
type family Arguments' args f where
  Arguments' args (a -> b) = Arguments' (a ': args) b
  Arguments' args b = args

This would allow you to write:

myFunc :: forall f arguments output m.
  ( Monad m
  , Result f ~ m output
  , Arguments f ~ arguments
  ) => f -> m f
myFunc fn = pure fn

which would make available type variables output and arguments within the body of your function:

myFunc :: forall f arguments output m.
  ( MonadIO m
  , Result f ~ m output
  , Arguments f ~ arguments
  , Typeable arguments, Typeable output
  ) => f -> m f
myFunc fn = do
  liftIO . putStrLn $ "Result: " ++ show (typeRep (Proxy @output))
  liftIO . putStrLn $ "Arguments: " ++ show (typeRep (Proxy @arguments))
  pure fn

This may or may not be suitable for what you're trying to do, and it may or may not work in Purescript.

Anyway, here's a self-contained Haskell example:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

import Control.Monad.IO.Class
import Data.Typeable

type family Result f where
  Result (a -> b) = Result b
  Result b = b

type Arguments f = Arguments' '[] f
type family Arguments' args f where
  Arguments' args (a -> b) = Arguments' (a ': args) b
  Arguments' args b = args

myFunc :: forall f arguments output m.
  ( MonadIO m
  , Result f ~ m output
  , Arguments f ~ arguments
  , Typeable arguments, Typeable output
  ) => f -> m f
myFunc fn = do
  liftIO . putStrLn $ "Result: " ++ show (typeRep (Proxy @output))
  liftIO . putStrLn $ "Arguments: " ++ show (typeRep (Proxy @arguments))
  liftIO . putStrLn $ ""
  pure fn

resultType :: forall f r. (Result f ~ r, Typeable r) => f -> String
resultType _ = show $ typeRep (Proxy @r)

main :: IO ()
main = do
  _ <- myFunc (pure . length :: String -> IO Int)
  _ <- myFunc ((\x y -> pure (x+y)) :: Double -> Double -> IO Double)
  putStrLn "done"