I’m trying to use Shake and I stumbled upon the following problem: there’s no easy and convenient way to interpolate a string. I know about Text.Printf
— it’s not what I’m looking for. The interpolation syntax I’m talking about is like this:
(Make)
HEADERS_DIR := /some/path/to/dir
CFLAGS := "-g -I$(HEADERS_DIR) -O2"
(Python)
headers_dir = "/some/path/to/dir"
cflags = "-g -I{headers_dir} -O2".format(**locals())
And now compare this to possible Haskell’s solution. Firstly:
$ cabal install interpolatedstring-perl6
$ ghci -XExtendedDefaultRules -XQuasiQuotes
And then:
> import Text.InterpolatedString.Perl6 (qq)
> let headers_dir = "/path/to/some/dir"
> [qq| -g -I$headers_dir -O2 |] :: String
While I think Make goes maybe a step too far in its eagerness to interpolate even when not asked, I wonder why Haskell doesn’t have a format
function like Python does. It could parse the supplied string and pull the value with same name as referenced in pattern:
let headers_dir = "/path/to/some/dir"
format "-g -I$headers_dir -O2"
Is there a specific reason why it’s not implemented, or everyone is just happy with Text.Printf
‘s verbosity and/or lengthy setup of interpolation libraries?
Update: A lot of people misinterpret the question as asking about general string formatting, with possibility of precision specification in case of integers, etc. The examples provided demonstrate one specific case of interpolation, which is just “substitute the result of show a
at the place of name”. It does allow for simpler syntax, and it does not use a variadic function per se.
9
I suspect it’s done as quasiquoting because this will interpolate the string at compile time, and due to the pure nature of functions in Haskell this is possible while other languages lack such ability because of their impure nature.
Think about it, other languages have to do it during runtime each time which is going to be much slower than the compile time once and done quasi-quoted interpolation.
If you want to do it at runtime explicitly then you should be able to do some simple abstractions that take advantage of Haskell’s strings being lists, or you could create a different data structure that’s similar pretty easily – but I would go for the more efficient quasi-quoted approach if I were doing this in any code I actually cared about.
That said, I found on Hackage the Data.Text.Format package which appears to do C# style substitution pretty straight forward, here’s an answer showing an example on SO:
String formatting in Haskell – though as noted by Bryan O’Sullivan and others on that question that answer usess an out-dated package and there’s a newer one Bryan has created (my guess the Data.Text.Format package is his new one that should be used) so the usage may be slightly different from in that SO example, though appears fairly similar.
4
The best typesafe approach I can offer you is the formatting
library
{-# LANGUAGE OverloadedStrings #-}
import Formatting (format, (%))
import Formatting.ShortFormatters (t)
headers_dir = "/path/to/some/dir"
example = format ("-g -I" % t % " -O2") headers_dir
GHCi> example
"-g -I/path/to/some/dir -O2"
1
updated after question update
I feel that the python version:
"-g -I{headers_dir} -O2".format(**locals())
is an abuse of the language. So I wouldn’t say that python really offers local variable interpolation in strings. In fact, few languages have it.
Answering why language X doesn't have feature Y
often comes down to saying that the designers did not feel that it was worth it. In the case of local variable interpolation in strings, it is very much a niche thing used in languages focused on command line scripts. Which Haskell is not. The fact that you can do it in python only comes from the (too?) powerful reflection facilities of the language.
Now why is Text.InterpolatedString.Perl6
so complex to use? It relies on quasiquotations, which is not part of the haskell language. It is a ghc language extension, so you have to declare its usage somewhere. If you exclude the library installation & the language extension declaration, its usage is not really more complex than in your other examples, and is more safe.
3
In Python: the language actually specifies that variables can be referenced by name at runtime. In Haskell: names for everything except top level bindings tend to get erased during compile time and replaced with numeric references.
In your example: headers_dir = "/path/to/some/dir"
becomes something a bit more like 0x09039dc0 = "/path/to/some/dir"
by the time the code is actually run. This means that any reference to headers_dir
has to get replaced by the numeric reference at compile time, which is what the quasiquoter was for.
Without using quasiquoters or other parts of Template Haskell; you’d have to do something to manually preserve the variable names, such as putting the values in a map.
What’s wrong with quasiquoting? Check out Shakespeare (as mentioned in comments):
{-# LANGUAGE QuasiQuotes #-}
import Text.Shakespeare.Text
import Data.Text
runtimeInterpolatedString :: (ToText a, ToText b, ToText c) => a -> b -> c -> Text
runtimeInterpolatedString a b c = [lt|There once was #{a} that #{b} when #{c}.|]