autoapply: Template Haskell to automatically pass values to functions

[ bsd3, library, template-haskell ] [ Propose Tags ]

See readme.md


[Skip to Readme]
Versions [faq] 0.1.0.0, 0.2.0.0, 0.3, 0.4
Change log changelog.md
Dependencies base (>=4.12 && <5), logict, mtl, template-haskell, th-desugar (==1.11.*), transformers, unification-fd [details]
License BSD-3-Clause
Copyright (c) 2020 Joe Hermaszewski
Author
Maintainer Joe Hermaszewski <if.it.fits.i.sits@monoid.al>
Category Template Haskell
Home page https://github.com/expipiplus1/autoapply#readme
Bug tracker https://github.com/expipiplus1/autoapply/issues
Source repo head: git clone https://github.com/expipiplus1/autoapply
Uploaded by jophish at 2020-05-06T15:03:37Z
Distributions NixOS:0.4
Downloads 270 total (270 in the last 30 days)
Rating 2.0 (votes: 1) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Hackage Matrix CI
Docs uploaded by user
Build status unknown [no reports yet]

Modules

[Index] [Quick Jump]

Downloads

Maintainer's Corner

For package maintainers and hackage trustees


Readme for autoapply-0.4

[back to package description]

autoapply

A Template-Haskell program to automatically pass arguments to functions wherever the type fits.

@tomjaguarpaw noticed that this is like "Type-directed implicit parameters".

TL;DR

You have the following values and want to stir them together and see what sticks.

  • foo :: Monad m => A -> B -> C -> m D
  • getA :: App A
  • myC :: C

$(autoApply [] ['getA, 'myC] 'foo) will create \b -> getA >>= \a -> foo a b myC which has type B -> App D

or

autoApplyDecs reverse [] ['getA, 'myC] ['foo] will create oof :: B -> App D; oof b = do { a <- getA; foo a b myC }

Why to use it

One nice use-case is to avoiding writing boilerplate wrappers for using an API in your Monad stack. For instance imagine the following API.

data Instance; data ExtraOpenInfo; data Foo; data Bar; data Handle
openHandle  :: MonadIO m => Instance -> Maybe ExtraOpenInfo -> m Handle
closeHandle :: MonadIO m => Instance -> Handle -> m ()
useHandle   :: MonadIO m => Instance -> Handle -> Foo -> m Bar

You'd like to use this in your polysemy application, using the Input effect to pass the Instance handle around, and always passing Nothing for ExtraOpenInfo because you don't use that functionality and getting a Foo from some other constraint MyConstraint. You define the following values.

myExtraOpenInfo :: Maybe ExtraOpenInfo
myExtraOpenInfo = Nothing
getInstance :: Member (Input Instance) r => Sem r Instance
getInstance = input
getFoo :: MyConstraint m => m Foo
getFoo = ...

You then create the wrapped API thusly:

autoapplyDecs
  (<> "'") -- Function to transform the names of the wrapped functions
  ['myExtraOpenInfo, 'getInstance, 'getFoo]
    -- Potential arguments to pass which must subsume the argument type of the
    -- function.
  [] -- Potential arguments to pass which must unify with the argument type
  ['openHandle, 'closeHandle, 'useHandle] -- Functions to wrap

Which creates the following declarations:

openHandle'
  :: (Member (Input Instance) r, MonadIO (Sem r)) => Sem r Handle
closeHandle'
  :: (Member (Input Instance) r, MonadIO (Sem r)) => Handle -> Sem r ()
useHandle'
  :: (Member (Input Instance) r, MyConstraint (Sem r), MonadIO (Sem r))
  => Handle -> Sem r Bar

Notice:

  • Instance is supplied with the Member (Input Instance) r constraint
  • Foo is supplied by MyConstraint (Sem r)
  • ExtraOpenInfo is not present at all, being supplied internally by myExtraOpenInfo

To see the generated code (it's exactly what you'd expect) compile test/Types.hs with -ddump-splices.

How to use this

To generate a new top-level declaration you'll need:

  • The Name of a function to apply to some arguments.
  • The Names of some values to try and pass as arguments
    • values whose type must subsume the argument type (if you're unsure, you probably want this one)
    • values whose type must merely unify with the argument type
  • A way of generating a name for this declaration given the wrapped name :: String -> String.

The new declaration will be generated, equal to the wrapped one but using the supplied arguments wherever possible.

Arguments can be used in two ways:

  • As regular parameters

    • If the type of the argument matches directly
    • An example is applying takeWhile to not; not is passed as the a -> Bool argument to takeWhile. $(autoapply [] ['not] 'takeWhile) :: [Bool] -> [Bool]
  • Using a monadic bind

    • If the wrapped function returns a value of type m a and there exists an instance Monad m
    • If the argument is of type n a and there exists an instance Monad m
    • If m unifies with n
    • An example is applying putStrLn to getLine. The String result of getLine is passed to putStrLn $(autoapply [] ['getLine] 'putStrLn) :: IO ()

It's important to note that Monad instance checking only goes as far as template-haskell's reifyInstances. i.e. only the instance heads are checked.

Constraints are checked (up to reifyInstances) so it won't pass reverse to (+) for example.

Monadic binds are performed in the order of arguments passed to the wrapped function, and will be performed more than once if the argument is used multiple times.

You may want to either type your generated declarations manually (putting the type after the splice) or turn on -XNoMonomorphismRestriction if your arguments have polymorphic constraints.

Where to use it

  • In an expression context:

    • $(autoApply ['my, 'arguments] [] 'myFunction)
  • At the top level to generate several declarations

    • $(autoApplyDecs (funNameToNewFunName :: String -> String) ['my] ['arguments] ['myFunction, 'anotherFunction])

See also

This has a similar feel to some other programs which also generate Haskell expressions based on types.

There are a couple of differences here:

  • One doesn't need to specify the desired type up front, this tool will just go as far as it can.
  • This tool isn't doing any interesting proof search instead it's just "if it fits, I sits"