Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functions as arguments to be used in template haskell quote

This is partially a followup to Lift instance for a function?. However, the answer there is to either globally define the function or to rewrite it inside the quotation. However, we will be using foo a lot with different functions for f from within the scope of a let. This makes it just about impossible for us to define multiple global version of f. The latter solution, of writing our function within the quote, seem equivalent to writing a lift on functions.

So, is there any way of lifting functions taken as arguments to use in a template Haskell quotation?


A very contrived example:

foo.hs

{-# Language TemplateHaskell #-}
import Language.Haskell.TH

foo :: (Int->Int) -> Int -> ExpQ
foo f x = [|f x|]

g :: ExpQ
g = 
    let 
        f = (\x -> x+1)
        f' = (\x' -> f(x') + 1)
    in foo f' 0

Will fail with:

foo.hs:5:11:
    No instance for (Language.Haskell.TH.Syntax.Lift (Int -> Int))
      arising from a use of ‘Language.Haskell.TH.Syntax.lift’
    In the expression: Language.Haskell.TH.Syntax.lift f
    In the expression:
      [| f x |]
      pending(rn) [x, f]
    In an equation for ‘foo’:
        foo f x
          = [| f x |]
            pending(rn) [x, f]
like image 493
jek Avatar asked Sep 30 '22 23:09

jek


1 Answers

Lifting functions is not possible. There are however two possible alternatives that might work for your:

Only lift the result of the function, apply the function at compile time

In your special case, because you know at compile time both x and f, you can just compute f x at compile time and only splice the result:

foo :: (Int->Int) -> Int -> ExpQ
foo f x  = [| $(lift $ f x) |]
--       = lift $ f x
-- foo f = lift . f

This doesn't change the type signature of f, but it requires that you know all the arguments you want to give to f. You'll need to import Language.Haskell.TH.Syntax for the lift function.

Pass an expression for a function as an argument

If you cannot use the first solution, there is another alternative. Instead of passing the function, you now pass a splice for a function as an argument:

foo :: ExpQ -> Int -> ExpQ
foo f x = [| $f x |]

There are two disadvantages: First, you loose type-safety because it isn't checked that the splice really expands to something that can be applied to an Int. And you need to change your calling code, like this:

g :: ExpQ
g = 
    let 
        f =  [| \x -> x+1 |]
        f' = [| \x' -> $f x' + 1 |]
    in foo f' 0
like image 165
bennofs Avatar answered Oct 12 '22 22:10

bennofs