typelits-printf: Type-safe printf from parsing GHC TypeLits Symbol

[ bsd3, library, text ] [ Propose Tags ] [ Report a vulnerability ]

An extensible and type-safe printf from parsing GHC TypeLits Symbol literals, matching the semantics of P.printf from Text.Printf in base. The difference is that the variants here will always fail to compile if given arguments of the wrong type (or too many or too little arguments). Most of the variants also provide useful type feedback, telling you the type of arguments it expects and how many when queried with :t or with typed holes.

See README and documentation of GHC.TypeLits.Printf for more information


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0, 0.1.1.0, 0.2.0.0, 0.3.0.0
Change log CHANGELOG.md
Dependencies base (>=4.16 && <5), text [details]
Tested with ghc >=9.2
License BSD-3-Clause
Copyright (c) Justin Le 2019
Author Justin Le
Maintainer justin@jle.im
Category Text
Home page https://github.com/mstksg/typelits-printf#readme
Bug tracker https://github.com/mstksg/typelits-printf/issues
Source repo head: git clone https://github.com/mstksg/typelits-printf
Uploaded by jle at 2024-09-12T04:55:50Z
Distributions
Downloads 1116 total (32 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2024-09-12 [all 1 reports]

Readme for typelits-printf-0.3.0.0

[back to package description]

symbols-printf

(Heavily inspired by Data.Symbol.Examples.Printf)

ghci> putStrLn $ printf "You have %.2f dollars, %s" 3.62 "Luigi"
You have 3.62 dollars, Luigi

An extensible and type-safe printf from parsing GHC TypeLits Symbol literals, matching the semantics of Text.Printf in base (it actually uses the same formatting function under the hood). The difference is that your printfs 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.

As of GHC 9.10 and later (and version 0.3.0.0 of this library), it uses -XRequiredTypeArguments to allow you to pass in the printf spec literal directly as if it were a normal String literal.

[!NOTE] The printf function here is only type-safe in GHC 9.10 and higher. If you are before GHC 9.10, you should use printf' instead and -XTypeApplications syntax:

printf' @"You have %.2f dollars, %s" 3.62 "Luigi"

Note the @ before the string literal.

Looking at its type:

ghci> :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 -> String: take two arguments, and return a String. The first argument must be an instance of FormatType "f" (things that can be formatted by %f) and the second argument must be an instance of FormatType "s" (things that can be formatted by %s).

We can see this in action by progressively applying arguments:

ghci> :t printf "You have %.2f dollars, %s" 3.62
FormatType "s" arg1 => arg1 -> String

ghci> :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:

ghci> 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.

ghci> 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.

ghci> putStrLn $ printf "You have %.2f dollars, %s" 3.62 "Luigi"
You have 3.62 dollars, Luigi

ghci> 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.

You can extend functionality with formatting for your own types by providing instances of FormatType.

Caveats

You will most likely need to add -freduction-depth=0 for most strings longer than 30 characters-ish or so. Be aware that there is a compile-time cost per string, so if you notice your compile times getting long, try investigating this. It is written to be fully compatible with the Text.Printf module from base.

Moving to typechecker plugin based parsing does improve performance ... however, I'm not sure how to get around requiring every module using printf to require enabling the typechecker plugin, which isn't too great from a usability standpoint. Template Haskell based alternatives (like th-printf) already do require an extra pragma (for QuasiQuotes), though so it might not be too bad in comparison.

Comparisons

There are a few other options for type-safe printfs out on hackage, and they all differ in a few key ways. Some, like th-printf and safe-printf, offer Template Haskell-based ways to generate your printf functions. This package is intended as a "template-haskell free" alternative.

Some others, like safe-printf, formatting, printf-safe, xformat, and category-printf, require manually constructing your fomatters, and so you always need to duplicate double-quotes for string literals. This detracts from one of the main convenience aspects of printf, in my opinion.

"You have " % f' 2 % " dollars, " % s
-- vs
"You have %.2f dollars, %s"

However, calling these libraries "safe printf libraries" does not do them justice. A library like formatting is a feature-rich formatting library, handling things like dates and other useful formatting features in a first-class way that embraces Haskell idioms. This library here is merely a type-safe printf, emulating the features of base's printf and C printf(3).

Todo

  • Tests
  • Support for localization/dynamic strings. Should be possible, but we'd have to re-implement a subset of singletons.