Copyright | (c) Justin Le 2019 |
---|---|
License | BSD3 |
Maintainer | justin@jle.im |
Stability | experimental |
Portability | non-portable |
Safe Haskell | None |
Language | Haskell2010 |
An extensible and type-safe printf from parsing GHC TypeLits Symbol
literals, matching the semantics of printf
from Text.Printf in
base. The difference is that your printf
s will always fail to
compile if given arguments of the wrong type (or too many or too little
arguments). It also allows you to use types to help your development,
by telling you the type of arguments it expects and how many when
queried with :t
or with typed holes. See documentation in
Text.Printf for details on how this formats items of various types,
and the differences with C printf(3)
.
See printf
for the main function.
You can extend functionality with formatting for your own types by providing
instances of FormatType
.
Also in this module is pfmt
, which allows you to format individual
items according to a single format specifier.
Synopsis
- printf :: forall str fun. Printf str fun => fun
- printf_ :: Printf str fun => p str -> fun
- data PHelp
- pHelp :: PHelp -> String
- class FormatFun (ffs :: [Either Symbol FieldFormat]) fun
- class FormatType (t :: SChar) a where
- formatArg :: p t -> a -> FieldFormat -> ShowS
- type SChar = Symbol
- pfmt :: forall c a. FormatType c a => PFmt c -> a -> String
- data PFmt c
- mkPFmt :: forall str lst ff f w q m c. (Listify str lst, ff ~ ParseFmt_ lst, Reflect ff, ff ~ 'FF f w q m c) => PFmt c
- mkPFmt_ :: forall str lst ff f w q m c p. (Listify str lst, ff ~ ParseFmt_ lst, Reflect ff, ff ~ 'FF f w q m c) => p str -> PFmt c
Printf
printf :: forall str fun. Printf str fun => fun Source #
"Type-safe printf". Call it like
.printf
@"you have %.02f dollars, %s"
>>>
putStrLn $ printf @"You have %.2f dollars, %s" 3.62 "Luigi"
You have 3.62 dollars, Luigi
Looking at its type:
>>>
:t printf @"You have %.2f dollars, %s"
(FormatType "f" arg1, FormatType "s" arg2) => arg1 -> arg2 -> String
It tells you that the result is an arg1 -> arg2 ->
: take two
arguments, and return a String
String
. The first argument must be an instance of
(things that can be formatted by FormatType
"f"%f
) and the second argument
must be an instance of
(things that can be formatted by FormatType
"s"%s
).
We can see this in action by progressively applying arguments:
>>>
:t printf @"You have %.2f dollars, %s" 3.62
FormatType "s" arg1 => arg1 -> String>>>
:t printf @"You have %.2f dollars, %s" 3.62 "Luigi"
String
The type errors for forgetting to apply an argument (or applying too many arguments) are pretty clear:
>>>
putStrLn $ printf @"You have %.2f dollars, %s"
-- ERROR: Call to printf missing argument fulfilling "%.2f" -- Either provide an argument or rewrite the format string to not expect -- one.>>>
putStrLn $ printf @"You have %.2f dollars, %s" 3.62
-- ERROR: Call to printf missing argument fulfilling "%s" -- Either provide an argument or rewrite the format string to not expect -- one.>>>
putStrLn $ printf @"You have %.2f dollars, %s" 3.62 "Luigi"
You have 3.62 dollars, Luigi>>>
putStrLn $ printf @"You have %.2f dollars, %s" 3.62 "Luigi" 72
-- ERROR: An extra argument of type Integer was given to a call to printf -- Either remove the argument, or rewrite the format string to include the -- appropriate hole.
If you want to see some useful error messages for feedback, pHelp
can
be useful:
>>>
pHelp $ printf @"You have %.2f dollars, %s" 3.62
-- ERROR: Call to printf missing argument fulfilling "%s" -- Either provide an argument or rewrite the format string to not expect -- one.
Note that this also supports the "interpret as an IO action to print out
results" functionality that Text.Printf supports. This also supports
returning strict Text
and lazy Text
as
well.
printf_ :: Printf str fun => p str -> fun Source #
A version of printf
taking an explicit
proxy, which allows usage without TypeApplications
>>>
putStrLn $ printf_ (Proxy :: Proxy "You have %.2f dollars, %s") 3.62 "Luigi"
You have 3.62 dollars, Luigi
A useful tool for helping the type system give useful errors for
printf
:
>>>
printf @"You have ".2f" dollars, %s" 3.26 :: PHelp
-- ERROR: Call to printf missing argument fulfilling "%s" -- Either provide an argument or rewrite the format string to not expect -- one.
Mostly useful if you want to force a useful type error to help see what is going on.
See also pHelp
pHelp :: PHelp -> String Source #
A useful helper function for helping the type system give useful
errors for printf
:
>>>
pHelp $ printf @"You have %.2f dollars, %s" 3.62
-- ERROR: Call to printf missing argument fulfilling "%s" -- Either provide an argument or rewrite the format string to not expect -- one.
Mostly useful if you want to force a useful type error to help see what is going on.
class FormatFun (ffs :: [Either Symbol FieldFormat]) fun Source #
The typeclass supporting polyarity used by
printf
. It works in mostly the same way as
PrintfType
from Text.Printf, and similar the same as
FormatF
. Ideally, you will never have to
run into this typeclass or have to deal with it directly.
Every item in the first argument of FormatFun
is a chunk of the
formatting string, split between format holes (Right
) and string
chunks (Left
).
If you want to see some useful error messages for feedback, pHelp
can
be useful:
>>>
pHelp $ printf @"You have %.2f dollars, %s" 3.62
-- ERROR: Call to printf missing argument fulfilling "%s" -- Either provide an argument or rewrite the format string to not expect -- one.
Instances
(TypeError ('Text "Result type of a call to printf not sufficiently inferred." :$$: 'Text "Please provide an explicit type annotation or other way to help inference.") :: Constraint) => FormatFun ('[] :: [Either Symbol FieldFormat]) () Source # | |
Defined in GHC.TypeLits.Printf.Internal | |
a ~ String => FormatFun ('[] :: [Either Symbol FieldFormat]) a Source # | |
Defined in GHC.TypeLits.Printf.Internal | |
a ~ Char => FormatFun ('[] :: [Either Symbol FieldFormat]) Text Source # | |
a ~ Char => FormatFun ('[] :: [Either Symbol FieldFormat]) Text Source # | |
a ~ Char => FormatFun ('[] :: [Either Symbol FieldFormat]) PHelp Source # | |
a ~ () => FormatFun ('[] :: [Either Symbol FieldFormat]) (IO a) Source # | |
(TypeError ((('Text "An extra argument of type " :<>: 'ShowType a) :<>: 'Text " was given to a call to printf.") :$$: 'Text "Either remove the argument, or rewrite the format string to include the appropriate hole") :: Constraint) => FormatFun ('[] :: [Either Symbol FieldFormat]) (a -> b) Source # | |
Defined in GHC.TypeLits.Printf.Internal | |
(KnownSymbol str, FormatFun ffs fun) => FormatFun (('Left str :: Either Symbol FieldFormat) ': ffs) fun Source # | |
(TypeError (MissingError ff) :: Constraint) => FormatFun (('Right ff :: Either Symbol FieldFormat) ': ffs) PHelp Source # | |
(TypeError (MissingError ff) :: Constraint) => FormatFun (('Right ff :: Either Symbol FieldFormat) ': ffs) Text Source # | |
(TypeError (MissingError ff) :: Constraint) => FormatFun (('Right ff :: Either Symbol FieldFormat) ': ffs) Text Source # | |
(TypeError (MissingError ff) :: Constraint) => FormatFun (('Right ff :: Either Symbol FieldFormat) ': ffs) () Source # | |
(TypeError (MissingError ff) :: Constraint) => FormatFun (('Right ff :: Either Symbol FieldFormat) ': ffs) String Source # | |
(afun ~ (arg -> fun), Reflect ff, ff ~ 'FF f w p m c, FormatType c arg, FormatFun ffs fun) => FormatFun (('Right ff :: Either Symbol FieldFormat) ': ffs) afun Source # | |
(TypeError (MissingError ff) :: Constraint) => FormatFun (('Right ff :: Either Symbol FieldFormat) ': ffs) (IO a) Source # | |
Formattable things
class FormatType (t :: SChar) a where Source #
Typeclass associating format types (d
, f
, etc.) with the types
that can be formatted by them.
You can extend the printf methods here for your own types by writing your instances here.
Nothing
formatArg :: p t -> a -> FieldFormat -> ShowS Source #
Instances
Single item
pfmt :: forall c a. FormatType c a => PFmt c -> a -> String Source #
Parse and run a single format hole on a single vale. Can be useful
for formatting individual items or for testing your own custom instances of
FormatType
.
Usually meant to be used with OverloadedLabels:
>>>
pfmt #f 3.62
"3.62"
However, current versions of GHC disallow labels that aren't valid
identifier names, disallowing things like
. While
there is an
<https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0170-unrestricted-overloadedlabels.rst
approved proposal> that allows this, if you are using an earlier GHC
version, you can get around this using pfmt
#.2f 3.62mkPFmt
:
>>>
pfmt (mkPFmt @".2f") 3.6234124
"3.62"
Ideally we'd want to be able to write
>>>
pfmt #.2f 3.6234124
"3.62"
(which should be possible in GHC 8.10+)
Note that the format string should not include the leading %
.
Utility type powering pfmt
. See documentation for pfmt
for more
information on usage.
Using OverloadedLabels, you never need to construct this directly
can just write #f
and a
will be generated. You can also
create this using PFmt
"f"mkPFmt
or mkPFmt_
, in the situations where
OverloadedLabels doesn't work or is not wanted.
mkPFmt :: forall str lst ff f w q m c. (Listify str lst, ff ~ ParseFmt_ lst, Reflect ff, ff ~ 'FF f w q m c) => PFmt c Source #
Useful for using pfmt
without OverloadedLabels, or also when
passing format specifiers that aren't currently allowed with
OverloadedLabels until GHC 8.10+ (like #.2f
).
>>>
pfmt (mkPFmt @".2f") 3.6234124
"3.62"