GHC issueshttps://gitlab.haskell.org/ghc/ghc/-/issues2019-07-07T18:02:48Zhttps://gitlab.haskell.org/ghc/ghc/-/issues/15833Typed template haskell quote fails to typecheck when spliced due to an ambigu...2019-07-07T18:02:48ZMatthew PickeringTyped template haskell quote fails to typecheck when spliced due to an ambiguous type variableIt should be the case that a code value constructed using typed template haskell should never fail to type check when spliced. Running `ghc Test.hs` with the following two modules produces an error about an ambiguous type variable.
http...It should be the case that a code value constructed using typed template haskell should never fail to type check when spliced. Running `ghc Test.hs` with the following two modules produces an error about an ambiguous type variable.
https://gist.github.com/5890c14dda73da738d2041c7f677b786
```
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wall #-}
module Compiler where
import Language.Haskell.TH
data Operator = Scan
| Join Operator Operator deriving Show
queryJoin :: Operator
queryJoin = Join Scan Scan
type QTExp a = Q (TExp a)
fix :: (a -> a) -> a
fix f = let x = f x in x
while ::
Monoid m =>
QTExp (IO m -> IO m) -> QTExp (IO m)
while b = [|| fix (\r -> whenM True ($$b r)) ||]
whenM :: Monoid m => Bool -> m -> m
whenM b act = if b then act else mempty
execOp :: Monoid m => Operator -> QTExp (IO m) -> QTExp (IO m)
execOp op yld =
case op of
Scan ->
while [|| \r -> ($$(yld) >> r)||]
Join left right ->
execOp left (execOp right yld)
runQuery :: QTExp (IO ())
runQuery = execOp (Join Scan Scan) ([|| return () ||])
```
```
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -ddump-splices #-}
module Test where
import qualified Compiler as C
main :: IO ()
main = do
$$(C.runQuery)
```
```
Test.hs:9:6: error:
• Ambiguous type variable ‘a0’ arising from a use of ‘C.whenM’
prevents the constraint ‘(Monoid a0)’ from being solved.
Relevant bindings include r_a5GX :: IO a0 (bound at Test.hs:9:6)
Probable fix: use a type annotation to specify what ‘a0’ should be.
These potential instances exist:
instance Monoid a => Monoid (IO a) -- Defined in ‘GHC.Base’
instance Monoid Ordering -- Defined in ‘GHC.Base’
instance Semigroup a => Monoid (Maybe a) -- Defined in ‘GHC.Base’
...plus 7 others
(use -fprint-potential-instances to see them all)
• In the expression:
(C.whenM True) ((\ r_a5GY -> ((return ()) >> r_a5GY)) r_a5GX)
In the first argument of ‘C.fix’, namely
‘(\ r_a5GX
-> (C.whenM True) ((\ r_a5GY -> ((return ()) >> r_a5GY)) r_a5GX))’
In the first argument of ‘(>>)’, namely
‘(C.fix
(\ r_a5GX
-> (C.whenM True) ((\ r_a5GY -> ((return ()) >> r_a5GY)) r_a5GX)))’
|
9 | $$(C.runQuery)
|
```
The generated code
```
Test.hs:9:6-15: Splicing expression
C.runQuery
======>
C.fix
(\ r_a5GV
-> (C.whenM True)
((\ r_a5GW
-> ((C.fix
(\ r_a5GX
-> (C.whenM True)
((\ r_a5GY -> ((return GHC.Tuple.()) >> r_a5GY)) r_a5GX)))
>> r_a5GW))
r_a5GV))
```
<details><summary>Trac metadata</summary>
| Trac field | Value |
| ---------------------- | ------------ |
| Version | 8.6.1 |
| Type | Bug |
| TypeOfFailure | OtherFailure |
| Priority | normal |
| Resolution | Unresolved |
| Component | Compiler |
| Test case | |
| Differential revisions | |
| BlockedBy | |
| Related | |
| Blocking | |
| CC | |
| Operating system | |
| Architecture | |
</details>
<!-- {"blocked_by":[],"summary":"Typed template haskell quote fails to typecheck when spliced due to an ambiguous type variable","status":"New","operating_system":"","component":"Compiler","related":[],"milestone":"","resolution":"Unresolved","owner":{"tag":"Unowned"},"version":"8.6.1","keywords":[],"differentials":[],"test_case":"","architecture":"","cc":[""],"type":"Bug","description":"It should be the case that a code value constructed using typed template haskell should never fail to type check when spliced. Running `ghc Test.hs` with the following two modules produces an error about an ambiguous type variable. \r\n\r\nhttps://gist.github.com/5890c14dda73da738d2041c7f677b786\r\n\r\n\r\n{{{\r\n{-# LANGUAGE TemplateHaskell #-}\r\n{-# OPTIONS_GHC -Wall #-}\r\nmodule Compiler where\r\n\r\nimport Language.Haskell.TH\r\n\r\ndata Operator = Scan\r\n | Join Operator Operator deriving Show\r\n\r\nqueryJoin :: Operator\r\nqueryJoin = Join Scan Scan\r\n\r\ntype QTExp a = Q (TExp a)\r\n\r\nfix :: (a -> a) -> a\r\nfix f = let x = f x in x\r\n\r\nwhile ::\r\n Monoid m =>\r\n QTExp (IO m -> IO m) -> QTExp (IO m)\r\nwhile b = [|| fix (\\r -> whenM True ($$b r)) ||]\r\n\r\nwhenM :: Monoid m => Bool -> m -> m\r\nwhenM b act = if b then act else mempty\r\n\r\nexecOp :: Monoid m => Operator -> QTExp (IO m) -> QTExp (IO m)\r\nexecOp op yld =\r\n case op of\r\n Scan ->\r\n while [|| \\r -> ($$(yld) >> r)||]\r\n Join left right ->\r\n execOp left (execOp right yld)\r\n\r\nrunQuery :: QTExp (IO ())\r\nrunQuery = execOp (Join Scan Scan) ([|| return () ||])\r\n}}}\r\n\r\n{{{\r\n\r\n{-# LANGUAGE TemplateHaskell #-}\r\n{-# OPTIONS_GHC -ddump-splices #-}\r\nmodule Test where\r\n\r\nimport qualified Compiler as C\r\n\r\nmain :: IO ()\r\nmain = do\r\n$$(C.runQuery)\r\n\r\n}}}\r\n\r\n{{{\r\nTest.hs:9:6: error:\r\n • Ambiguous type variable ‘a0’ arising from a use of ‘C.whenM’\r\n prevents the constraint ‘(Monoid a0)’ from being solved.\r\n Relevant bindings include r_a5GX :: IO a0 (bound at Test.hs:9:6)\r\n Probable fix: use a type annotation to specify what ‘a0’ should be.\r\n These potential instances exist:\r\n instance Monoid a => Monoid (IO a) -- Defined in ‘GHC.Base’\r\n instance Monoid Ordering -- Defined in ‘GHC.Base’\r\n instance Semigroup a => Monoid (Maybe a) -- Defined in ‘GHC.Base’\r\n ...plus 7 others\r\n (use -fprint-potential-instances to see them all)\r\n • In the expression:\r\n (C.whenM True) ((\\ r_a5GY -> ((return ()) >> r_a5GY)) r_a5GX)\r\n In the first argument of ‘C.fix’, namely\r\n ‘(\\ r_a5GX\r\n -> (C.whenM True) ((\\ r_a5GY -> ((return ()) >> r_a5GY)) r_a5GX))’\r\n In the first argument of ‘(>>)’, namely\r\n ‘(C.fix\r\n (\\ r_a5GX\r\n -> (C.whenM True) ((\\ r_a5GY -> ((return ()) >> r_a5GY)) r_a5GX)))’\r\n |\r\n9 | $$(C.runQuery)\r\n | \r\n}}}\r\n\r\nThe generated code\r\n\r\n{{{\r\nTest.hs:9:6-15: Splicing expression\r\n C.runQuery\r\n ======>\r\n C.fix\r\n (\\ r_a5GV\r\n -> (C.whenM True)\r\n ((\\ r_a5GW\r\n -> ((C.fix\r\n (\\ r_a5GX\r\n -> (C.whenM True)\r\n ((\\ r_a5GY -> ((return GHC.Tuple.()) >> r_a5GY)) r_a5GX)))\r\n >> r_a5GW))\r\n r_a5GV))\r\n}}}","type_of_failure":"OtherFailure","blocking":[]} -->https://gitlab.haskell.org/ghc/ghc/-/issues/15437Internal error when applying a scoped type variable inside a typed expression...2023-08-11T11:04:21ZdminuosoInternal error when applying a scoped type variable inside a typed expression quotation```hs
{-# LANGUAGE TemplateHaskell #-}
import TestMod
f :: Int
f = $$(foo)
main :: IO ()
main = main
```
```hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
module TestMod wh...```hs
{-# LANGUAGE TemplateHaskell #-}
import TestMod
f :: Int
f = $$(foo)
main :: IO ()
main = main
```
```hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
module TestMod where
import Language.Haskell.TH.Syntax (Q, TExp)
get :: forall a. Int
get = 1
foo :: forall a. Q (TExp Int)
foo = [|| get @a ||]
```
```
Test.hs:6:8: error:
• The exact Name ‘a’ is not in scope
Probable cause: you used a unique Template Haskell name (NameU),
perhaps via newName, but did not bind it
If that's it, then -ddump-splices might be useful
• In the result of the splice:
$foo
To see what the splice expanded to, use -ddump-splices
In the Template Haskell splice $$(foo)
In the expression: $$(foo)
|
6 | f = $$(foo)
| ^^^
Test.hs:6:8: error:
• GHC internal error: ‘a’ is not in scope during type checking, but it passed the renamer
tcl_env of environment: [r3Kl :-> Identifier[f::Int, TopLevelLet [] True],
r3PI :-> Identifier[main::IO (), TopLevelLet [r3PI :-> main] True]]
• In the type ‘a’
In the expression: get @a
In the result of the splice:
$foo
To see what the splice expanded to, use -ddump-splices
|
6 | f = $$(foo)
|
```
<details><summary>Trac metadata</summary>
| Trac field | Value |
| ---------------------- | ------------ |
| Version | 8.4.3 |
| Type | Bug |
| TypeOfFailure | OtherFailure |
| Priority | normal |
| Resolution | Unresolved |
| Component | Compiler |
| Test case | |
| Differential revisions | |
| BlockedBy | |
| Related | |
| Blocking | |
| CC | |
| Operating system | |
| Architecture | |
</details>
<!-- {"blocked_by":[],"summary":"Internal error when applying a scoped type variable inside a typed expression quotation","status":"New","operating_system":"","component":"Compiler","related":[],"milestone":"8.6.1","resolution":"Unresolved","owner":{"tag":"Unowned"},"version":"8.4.3","keywords":[],"differentials":[],"test_case":"","architecture":"","cc":[""],"type":"Bug","description":"{{{#!hs\r\n{-# LANGUAGE TemplateHaskell #-}\r\n\r\nimport TestMod\r\n\r\nf :: Int\r\nf = $$(foo)\r\n\r\nmain :: IO ()\r\nmain = main\r\n}}}\r\n\r\n{{{#!hs\r\n{-# LANGUAGE ScopedTypeVariables #-}\r\n{-# LANGUAGE TemplateHaskell #-}\r\n{-# LANGUAGE TypeApplications #-}\r\n\r\nmodule TestMod where\r\n\r\nimport Language.Haskell.TH.Syntax (Q, TExp)\r\n\r\nget :: forall a. Int\r\nget = 1\r\n\r\nfoo :: forall a. Q (TExp Int)\r\nfoo = [|| get @a ||]\r\n}}}\r\n\r\n{{{\r\nTest.hs:6:8: error:\r\n • The exact Name ‘a’ is not in scope\r\n Probable cause: you used a unique Template Haskell name (NameU),\r\n perhaps via newName, but did not bind it\r\n If that's it, then -ddump-splices might be useful\r\n • In the result of the splice:\r\n $foo\r\n To see what the splice expanded to, use -ddump-splices\r\n In the Template Haskell splice $$(foo)\r\n In the expression: $$(foo)\r\n |\r\n6 | f = $$(foo)\r\n | ^^^\r\n\r\nTest.hs:6:8: error:\r\n • GHC internal error: ‘a’ is not in scope during type checking, but it passed the renamer\r\n tcl_env of environment: [r3Kl :-> Identifier[f::Int, TopLevelLet [] True],\r\n r3PI :-> Identifier[main::IO (), TopLevelLet [r3PI :-> main] True]]\r\n • In the type ‘a’\r\n In the expression: get @a\r\n In the result of the splice:\r\n $foo\r\n To see what the splice expanded to, use -ddump-splices\r\n |\r\n6 | f = $$(foo)\r\n |\r\n}}}","type_of_failure":"OtherFailure","blocking":[]} -->8.10.2https://gitlab.haskell.org/ghc/ghc/-/issues/13587addTopDecls fails with typed Template Haskell2019-07-07T18:21:09ZTrevor L. McDonelladdTopDecls fails with typed Template HaskellThe following untyped Template Haskell works as expected:
```hs
--- AddTopDecls.hs ---
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
module AddTopDecls where
import Language.Haskell.TH
import Language.Haskell.TH.Sy...The following untyped Template Haskell works as expected:
```hs
--- AddTopDecls.hs ---
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
module AddTopDecls where
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
importDoubleToDouble :: String -> ExpQ
importDoubleToDouble fname = do
n <- newName fname
d <- forImpD CCall unsafe fname n [t|Double -> Double|]
addTopDecls [d]
varE n
--- Main.hs ---
{-# LANGUAGE TemplateHaskell #-}
module Main where
import AddTopDecls
main :: IO ()
main = do
let sin' = $(importDoubleToDouble "sin")
cos' = $(importDoubleToDouble "cos")
--
print (sin' 0)
print (cos' pi)
```
However it fails if I convert to the equivalent typed version:
```hs
--- AddTopDecls.hs ---
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
module AddTopDecls where
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
importDoubleToDouble :: String -> Q (TExp (Double -> Double))
importDoubleToDouble fname = do
n <- newName fname
d <- forImpD CCall unsafe fname n [t|Double -> Double|]
addTopDecls [d]
unsafeTExpCoerce (varE n)
--- Main.hs ---
{-# LANGUAGE TemplateHaskell #-}
module Main where
import AddTopDecls
main :: IO ()
main = do
let sin' = $$(importDoubleToDouble "sin")
cos' = $$(importDoubleToDouble "cos")
--
print (sin' 0)
print (cos' pi)
```
With the error:
```
> ghci Main.hs -ddump-splices
GHCi, version 8.2.0.20170404: http://www.haskell.org/ghc/ :? for help
[1 of 2] Compiling AddTopDecls ( AddTopDecls.hs, interpreted )
[2 of 2] Compiling Main ( Main.hs, interpreted )
Main.hs:9:19-44: Splicing expression
importDoubleToDouble "sin" ======> sin_a4s2
Main.hs:1:1: Splicing top-level declarations added with addTopDecls
======>
foreign import ccall unsafe "sin" Main.sin :: Double -> Double
Main.hs:9:19: error:
• GHC internal error: ‘sin_a4s2’ is not in scope during type checking, but it passed the renamer
tcl_env of environment: [a4dl :-> Identifier[sin'::t1, TopLevelLet [] False],
a4dm :-> Identifier[cos'::t1, TopLevelLet [] False],
r4cW :-> Identifier[main::IO (), TopLevelLet]]
• In the expression: sin_a4s2
In the result of the splice:
$importDoubleToDouble "sin"
To see what the splice expanded to, use -ddump-splices
In the Template Haskell splice $$(importDoubleToDouble "sin")
|
9 | let sin' = $$(importDoubleToDouble "sin")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
```
Tested with 7.10.3, 8.0.2, and 8.2.0-rc1.
Unfortunately I can't use untyped TH in my real use case, so if you have any suggestions for a workaround that would also be great.
<details><summary>Trac metadata</summary>
| Trac field | Value |
| ---------------------- | ---------------- |
| Version | 8.2.1-rc1 |
| Type | Bug |
| TypeOfFailure | OtherFailure |
| Priority | normal |
| Resolution | Unresolved |
| Component | Template Haskell |
| Test case | |
| Differential revisions | |
| BlockedBy | |
| Related | |
| Blocking | |
| CC | |
| Operating system | |
| Architecture | |
</details>
<!-- {"blocked_by":[],"summary":"addTopDecls fails with typed Template Haskell","status":"New","operating_system":"","component":"Template Haskell","related":[],"milestone":"","resolution":"Unresolved","owner":{"tag":"Unowned"},"version":"8.2.1-rc1","keywords":[],"differentials":[],"test_case":"","architecture":"","cc":[""],"type":"Bug","description":"The following untyped Template Haskell works as expected:\r\n\r\n{{{#!hs\r\n--- AddTopDecls.hs ---\r\n{-# LANGUAGE TemplateHaskell #-}\r\n{-# LANGUAGE QuasiQuotes #-}\r\n\r\nmodule AddTopDecls where\r\n\r\nimport Language.Haskell.TH\r\nimport Language.Haskell.TH.Syntax\r\n\r\nimportDoubleToDouble :: String -> ExpQ\r\nimportDoubleToDouble fname = do\r\n n <- newName fname\r\n d <- forImpD CCall unsafe fname n [t|Double -> Double|]\r\n addTopDecls [d]\r\n varE n\r\n\r\n--- Main.hs ---\r\n{-# LANGUAGE TemplateHaskell #-}\r\n\r\nmodule Main where\r\n\r\nimport AddTopDecls\r\n\r\nmain :: IO ()\r\nmain = do\r\n let sin' = $(importDoubleToDouble \"sin\")\r\n cos' = $(importDoubleToDouble \"cos\")\r\n --\r\n print (sin' 0)\r\n print (cos' pi)\r\n}}}\r\n\r\nHowever it fails if I convert to the equivalent typed version:\r\n\r\n{{{#!hs\r\n--- AddTopDecls.hs ---\r\n{-# LANGUAGE TemplateHaskell #-}\r\n{-# LANGUAGE QuasiQuotes #-}\r\n\r\nmodule AddTopDecls where\r\n\r\nimport Language.Haskell.TH\r\nimport Language.Haskell.TH.Syntax\r\n\r\nimportDoubleToDouble :: String -> Q (TExp (Double -> Double))\r\nimportDoubleToDouble fname = do\r\n n <- newName fname\r\n d <- forImpD CCall unsafe fname n [t|Double -> Double|]\r\n addTopDecls [d]\r\n unsafeTExpCoerce (varE n)\r\n\r\n--- Main.hs ---\r\n{-# LANGUAGE TemplateHaskell #-}\r\n\r\nmodule Main where\r\n\r\nimport AddTopDecls\r\n\r\nmain :: IO ()\r\nmain = do\r\n let sin' = $$(importDoubleToDouble \"sin\")\r\n cos' = $$(importDoubleToDouble \"cos\")\r\n --\r\n print (sin' 0)\r\n print (cos' pi)\r\n}}}\r\n\r\nWith the error:\r\n\r\n{{{\r\n> ghci Main.hs -ddump-splices\r\nGHCi, version 8.2.0.20170404: http://www.haskell.org/ghc/ :? for help\r\n[1 of 2] Compiling AddTopDecls ( AddTopDecls.hs, interpreted )\r\n[2 of 2] Compiling Main ( Main.hs, interpreted )\r\nMain.hs:9:19-44: Splicing expression\r\n importDoubleToDouble \"sin\" ======> sin_a4s2\r\nMain.hs:1:1: Splicing top-level declarations added with addTopDecls\r\n ======>\r\n foreign import ccall unsafe \"sin\" Main.sin :: Double -> Double\r\n\r\nMain.hs:9:19: error:\r\n • GHC internal error: ‘sin_a4s2’ is not in scope during type checking, but it passed the renamer\r\n tcl_env of environment: [a4dl :-> Identifier[sin'::t1, TopLevelLet [] False],\r\n a4dm :-> Identifier[cos'::t1, TopLevelLet [] False],\r\n r4cW :-> Identifier[main::IO (), TopLevelLet]]\r\n • In the expression: sin_a4s2\r\n In the result of the splice:\r\n $importDoubleToDouble \"sin\"\r\n To see what the splice expanded to, use -ddump-splices\r\n In the Template Haskell splice $$(importDoubleToDouble \"sin\")\r\n |\r\n9 | let sin' = $$(importDoubleToDouble \"sin\")\r\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n}}}\r\n\r\nTested with 7.10.3, 8.0.2, and 8.2.0-rc1.\r\n\r\nUnfortunately I can't use untyped TH in my real use case, so if you have any suggestions for a workaround that would also be great.","type_of_failure":"OtherFailure","blocking":[]} -->https://gitlab.haskell.org/ghc/ghc/-/issues/17212Allow default methods to be implemented using template haskell2023-06-12T14:48:02ZMatthew PickeringAllow default methods to be implemented using template haskellIn conversations with @kosmikus he suggested that it would be nice to integrate default implementations with typed template haskell.
For example,
```
class Eq a where
eq :: a -> a -> Bool
default splice eq :: Generic a => Cod...In conversations with @kosmikus he suggested that it would be nice to integrate default implementations with typed template haskell.
For example,
```
class Eq a where
eq :: a -> a -> Bool
default splice eq :: Generic a => Code (a -> a -> Bool)
eq = _impl_
```
This has the massive advantage that the default implementation can be efficient than the usual `Data` or `Generics` based implementations.
There would need to be a GHC proposal related to this ticket but I write it down here in case anyone else finds it interesting.
Related to #12457Matthew PickeringMatthew Pickeringhttps://gitlab.haskell.org/ghc/ghc/-/issues/16524unused-top-binds Warning is Disabled in the Presence of TemplateHaskell2022-03-28T05:18:02Zrprijeunused-top-binds Warning is Disabled in the Presence of TemplateHaskell# Summary
If `TemplateHaskell` is enabled and a template haskell splice is used in code used by exported toplevel binds, then `unused-top-binds` fails to warn on any unused toplevel binds.
# Steps to reproduce
Given this code:
```
{-...# Summary
If `TemplateHaskell` is enabled and a template haskell splice is used in code used by exported toplevel binds, then `unused-top-binds` fails to warn on any unused toplevel binds.
# Steps to reproduce
Given this code:
```
{-# LANGUAGE TemplateHaskell #-}
module Main (main) where
import Foo
breakTopBindCheck :: IO ()
breakTopBindCheck = let _ = $$(foo 'f') in pure ()
normalOperation :: IO ()
normalOperation = pure ()
main :: IO ()
main = run
where
-- run = normalOperation
run = breakTopBindCheck
```
And any template haskell implementation of foo. For example:
```
{-# LANGUAGE TemplateHaskell #-}
module Foo (foo) where
import Language.Haskell.TH (Q)
import Language.Haskell.TH.Syntax (Exp (..), Lit (..), TExp (..))
foo :: Char -> Q (TExp Char)
foo c = pure $ TExp $ LitE $ CharL c
```
The Main module, when compiled, will fail to complain about the unused top binding for the `normalOperation` function. If the `run` definition binding to `breakTopBindCheck` is commented out, and the `run` definition binding to `normalOperation` is uncommented, then compiling successfully warns about the unused `breakTopBindCheck` binding.
# Expected behavior
I expect unused top binds to result in warnings when unused top binds warnings are enabled, regardless of whether TemplateHaskell is in use.
# Environment
* GHC version used: 8.4.4
Optional:
* Operating System: Linux, NixOS, Kernel 4.14.107
* System Architecture: x86-64https://gitlab.haskell.org/ghc/ghc/-/issues/10271Typed Template Haskell splice difficulty when resolving overloading2020-08-25T09:55:38ZSimon Peyton JonesTyped Template Haskell splice difficulty when resolving overloadingJ Garrett Morris describes the following surprising behaviour for typed Template Haskell
```
{-# LANGUAGE TemplateHaskell, FlexibleInstances #-}
module PrintfLib where
import Language.Haskell.TH
class Printf t where
print...J Garrett Morris describes the following surprising behaviour for typed Template Haskell
```
{-# LANGUAGE TemplateHaskell, FlexibleInstances #-}
module PrintfLib where
import Language.Haskell.TH
class Printf t where
printf :: String -> Q (TExp String) -> Q (TExp t)
instance Printf [Char] where
tf s t | '%' `notElem` s = [|| $$t ++ s ||]
| otherwise = fail ("Unexpected format %"
++ [c])
where (_, _:c:_) = break ('%' ==) s
instance Printf t => Printf (Char -> t) where
printf s t
| c /= 'c' = fail ("Unexpected format %" ++ [c] ++
" for character")
| otherwise = [|| \c -> $$(printf s''
[|| $$t ++ s' ++ [c] ||])
||]
where (s', '%':c:s'') = break ('%' ==) s
-------------------------
{-# LANGUAGE TemplateHaskell #-}
module Printf where
import PrintfLib
f :: Char -> String
f = $$(printf "foo %c" [||""||])
h :: Char -> String
h y = $$(printf "foo %c" [||""||]) y
```
Oddly, `f` typechecks but `h` does not, even though `h` is just an eta-expanded version of `f`:
```
Printf.hs:9:10:
No instance for (Printf t0) arising from a use of ‘printf’
The type variable ‘t0’ is ambiguous
Note: there are several potential instances:
instance Printf t => Printf (Char -> t) -- Defined in ‘PrintfLib’
instance Printf [Char] -- Defined in ‘PrintfLib’
In the expression: printf "foo %c" [|| "" ||]
```
What is going on? Here's the deal
- To run the splice, GHC must solve any constraints that arise form the expression `(printf "foo %c" ...)`.
- Since `printf` is overloaded, and overloaded on its result type, the type needed by the context of the splice is what determines which instance of `Printf` is needed.
- In `f` the context needs `Char -> String`, and so the call to `printf` must have type `TExpr (Char -> String)`, so we get the constraint `Printf (Char -> String)` which we can solve.
- But in `h` the rule for application tries to *infer* a type for the splice. So the context for the call just says `TExp t0` for some unification variable `t0`; and that leads to the insoluble constraint.
You may say that GHC should be cleverer, and push that type signature information into the application. And perhaps it should. But you can never win. For example:
```
hard x = [ $$(printf "gluk" [|| "" ||]), undefined :: Char -> String ]
```
Here the RHS of `hard` is a 2-element list. Since all elements of a list have the same type, the splice must have the same type as the second element of the list, namely `Char->String`. But seeing that would mean that we'd have to typecheck right-to-left. In general GHC tries very very hard NOT to depend on traversal order. There is no way in general to ensure that we have all the information now that constraint solving may ultimately produce.
I'm not sure what to do about this.
- It seldom matters, because resolving the overloading on the splice seldom depends on the result type.
- When it does matter, you can fix it by giving a type signature to the splice itself.
But it seems unsatisfactory. Ideas welcome.