8

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.

Michael Pankov
  • 568
  • 1
  • 5
  • 15
  • The full functionality of `printf` is one thing. That's hard to do in a type-safe way. String interpolation is something different. Why not just `format ("-g " ++ headers_dir ++ " -O2")`? – Tom Ellis Jan 24 '14 at 15:49
  • @TomEllis: For the same reason we don't do that in other languages. It's verbose, makes things much more difficult to localize, and is more prone to bugs. – Phoshi Jan 24 '14 at 16:00
  • 1
    @TomEllis Because it has awful readability, introduces unnecessary operators, and makes it easy to miss the space on either side of the variable substitution (or add unneeded ones). – Michael Pankov Jan 24 '14 at 16:00
  • 2
    @constantius: Arguably $-interpolation is less prone to mistakes, but you yourself missed off the "-I" in the last example! (A mistake which I copied verbatim). In general, if you do this kind of string interpolation a lot in Haskell you're doing something wrong. There's a better abstraction waiting to be implemented. – Tom Ellis Jan 24 '14 at 16:09
  • @TomEllis And not using $-interpolation in exchange for `printf` wouldn't save me from that mistake. – Michael Pankov Jan 24 '14 at 18:27
  • @constantius: You're right, it wouldn't save you. I'm just saying I don't think it's really the drawback you seem to think it is. Us Haskellers really do just put up with it, or use something like `formatting`, see below. – Tom Ellis Jan 24 '14 at 18:52
  • @TomEllis What kind of better abstraction do you have in mind? – Michael Pankov Jan 25 '14 at 11:49
  • The "better abstraction" depends on the use case. For something like a build system it is tricky since it seems to require a lot of string munging for interacting with the OS shell. However it wouldn't be unresonable to develop a small DSL for representing shell commands and their arguments. – Tom Ellis Jan 25 '14 at 14:13
  • You can also use [shakespeare-text](http://hackage.haskell.org/package/shakespeare-text), which may be of interest to you. – Ptharien's Flame Jan 25 '14 at 22:23

5 Answers5

4

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.

Jimmy Hoffa
  • 16,039
  • 3
  • 69
  • 80
  • 3
    If the quasi quotation is in a function with input parameters, the string will be produced at runtime. Also to be fair, most serious languages support some form of constant string folding at compile time. – Simon Bergot Jan 24 '14 at 16:55
  • @Simon good point; I wasn't certain of this behaviour of QQ, was wondering if it would just error at compile as "cannot do that" or it would just elevate it to runtime, thanks for the detail! – Jimmy Hoffa Jan 24 '14 at 17:13
  • 1
    Actually I believe that the inerpolated variables are typechecked. According to [the doc](http://hackage.haskell.org/package/interpolatedstring-perl6-0.4/docs/Text-InterpolatedString-Perl6.html) it should compile only if they are instances of the Show typeclass. – Simon Bergot Jan 24 '14 at 17:20
  • @Simon ah so my intuition was right and they are expanded at compile time with a big "I'm afraid I cannot do that, Dave" if you try to give it something that wouldn't be compile time expandable. The type checking being the key here that other languages won't have such guarantees from their types that would allow this. That said good point, many languages do have a compile-time expansion mechanism. – Jimmy Hoffa Jan 24 '14 at 17:35
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.

Simon Bergot
  • 7,930
  • 3
  • 35
  • 54
  • Please note that `format` as proposed in question shouldn't have *any* arguments, besides the format string. So it's not variadic per se. Also note that the question doesn't talk about general string interpolation with format specification, it proposes using directly the result of `show a`. – Michael Pankov Jan 25 '14 at 11:52
  • @constantius see the update – Simon Bergot Jan 25 '14 at 13:09
  • About "an abuse of the language": there is proposal for doing like this by default (`pep-0498`), and it was quite welcomed on proggit. Also there are template strings in javascript. – Shadows In Rain Dec 17 '15 at 07:09
1

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"
Tom Ellis
  • 177
  • 5
1

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}.|]
alrunner4
  • 21
  • 3
1

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.

Jeremy List
  • 111
  • 2