I don’t have any plans to package this.
Would it be possible to use the proposed implementation as a library?
The only issue with this implementation (which is pretty minor considering the brilliance of the solution and the minor inconvenience) is that the first argument must always be a String value.
For example, this code fails to compile:
interp 42 " apples and " 52 " oranges."
Could not match type
Int
with type
String
while checking that type Int
is at least as general as type String
while checking that expression 42
has type String
in value declaration main
I’m not yet sure whether it’s possible to get around that for the below reasons.
If I define a local binding that applies an empty String argument to interp
, I can get around this:
main = do
let interp' = interp ""
log $ interp' 42 bar "baz" true
However, using the same binding in two different ways will produce problems:
main = do
let interp' = interp ""
log $ interp' 42 "baz" true
log $ interp' 42 true "baz" true
-- `Boolean` (from true) does not unify with String (from "baz")
This is because of interp :: String -> a
, so the initial application is fixed to String
. You could probably reformulate this with just interp :: a
, or make interp :: a -> b
with a multi-parameter typeclass.
I wasn’t sure how to encode the type class using a multi-parameter typeclass. Everything I’ve tried runs into a problem sooner or later. Your solution seems to work only because the first argument is hard-coded to String.
I think if you just change i
to be
i = interp ""
you’re good.
Right, this is due to (lack of) let generalization. You need a type signature if it’s in a let.
i :: forall a. Interp a => a
i = interp ""
This is generalized in a top-level declaration, but not in a let binding.
Ah… that’s why my original interp' = interp ""
didn’t work.
Otherwise, yeah, this works:
i :: forall a. Interp a => a
i = interp ""
So, I’ve created purescript-interpolate
to do this, but I’m getting stuck when publishing this library.
How do I fix the issue?
Ah, that’s a problem. We will have to amend the instructions for publishing to Pursuit. Thanks for bringing this to my attention!
Library has been published. See its installation instructions. PR to the official package set builds and is awaiting review. If you have an idea for building off of this, see how to refer to it in your bower.json
file
Also, don’t use this library when doing a fold (e.g. foldl i "" arrayOfInts
) until the inliner optimization is done. See this benchmark, which I hope isn’t naive.
Edit: the above benchmark was implemented incorrectly. See this comment for an accurate one.
So I saw the benchmark, the blue line indicates standard append
and it blows up, while I guess the red should have, right?
I’ve run this benchmark with changed functions with one size = 10000.
Raw data results (because making a graph is beyond my skills )
-
foldl (\a b -> show a <> show b) "" array
causesJavaScript heap out of memory
-
0.001428
mean offoldl (\a b -> a <> show b) "" array
-
0.001227
mean offoldl i "" array
-
0.001106
mean offoldl interp "" array
In the original benchmark (1) is blue (the blow-up one) and (3) is red.
Remarks:
- Running
show
on an accumulated string breaks my memory - Difference between (2),(3),(4) is negligible - using fold with
interp
is ok here
I think the performance problem is not here, but when you write a long expression with interp
(or when it is generated with another typeclass generic magic)
Example
Simple usage of interp vs. append
fooI = i "a" 1 "b" 2 "c" 3
fooA = "a" <> show 1 <> "b" <> show 2 <> "c" <> show 3
And the compiled JS (without inlining)
var fooI = function (dictInterp) {
return Data_Interpolate.i(
Data_Interpolate.interpStringFunction(
Data_Interpolate.interpIntFunction(
Data_Interpolate.interpStringFunction(
Data_Interpolate.interpIntFunction(
Data_Interpolate.interpStringFunction(
Data_Interpolate.interpIntFunction(
dictInterp)))))))("a")(1)("b")(2)("c")(3);
};
var fooA = "a" + (Data_Show.show(Data_Show.showInt)(1) + ("b" + (Data_Show.show(Data_Show.showInt)(2) + ("c" + Data_Show.show(Data_Show.showInt)(3)))));
You can convert the outputted json file into a graph by uploading it here and then exporting the result as an SVG or PNG file: http://harry.garrood.me/purescript-benchotron-svg-renderer/
Looking at my original benchmark again, I realized I misread it. I thought the blue line was the i a b
one, not the a <> b
one…
So, the original benchmark I created used \acc next -> show acc <> show next
when it should have been \acc next -> acc <> show next
. @paluh pointed this out.
Here’s the correct benchmark, which better reflects my expectation:
While those work, they also feel boilerplate-y…
-- purescript-template-strings
"Hello, ${firstName} ${lastName}!" <~> { firstName: "Haskell", lastName: "Curry" }
"The answer: ${answer}" <~> { answer: 42 }
-- purescript-interpolate
i "Hello, " "Haskell" "Curry" "!"
i "The answer: "(show 42)
…and…
-- purescript-formatting
inbox :: forall r. Format String r (String -> Int -> r)
inbox = greeting <<< s " You have " <<< F.int <<< s " new messages."
message3 :: String
message3 = print inbox "Kris" 3
--> message3 == "Hello Kris! You have 3 new messages."
-- purescript-interpolate
inbox name amount = i "Hello " name "! You have "(show amount)" new messages."
message3 = inbox "Kris" 3
Where all of these solutions fall short is multi-line strings. I just made small election prediction project that used a string like
%s
Biden: %s
Trump: %s
I ended up using template-strings
because it was the only library that allowed me to preserve the string structure. That being said, I’m not a fan of having to do it this way and I definitely support native template strings.
So I just did a code generation project and none of the libraries posted here were sufficient - I needed multi-line string interpolation with the ability to type ${...}
literally because it was used in the code I was generating. I managed to find template-literals. Which is essentially just an FFI call to an eval
of a JS template literal. I went with that, but it wasn’t pleasant to use. So I bit the bullet and created my own string interpolation library that has more advanced features. I’m posting it here so it can be found by anyone else looking for this feature.
https://pursuit.purescript.org/packages/purescript-substitute/
feedback is welcome