serokell / universum

:milky_way: Prelude written in @Serokell
MIT License
174 stars 26 forks source link

Add string interpolation functionality #246

Closed Martoon-00 closed 2 years ago

Martoon-00 commented 2 years ago

After some small discussion, we decided that using string interpolation via TH can be considered cool after all, so we need to export some support for this feature.

We should consider using the existing libraries, hopefully, they will be enough to obtain the desired functionality or give us hints on a better interface, but if not - that should be fine to roll out our own solution.

To my knowledge, yet recently there was no interpolation library that would be just superb and could be picked as-is, so let's discuss what do we want.

Martoon-00 commented 2 years ago

My points:

  1. I should be able to get rid of the extra indentation automatically (optionally?).

Namely, this

myTxt = [i|
  My text
  Yay
  |]

should produce just My text\nYay string.

  1. I should be able to pass Buildable a => a, Show a => a, string literals (without the need to explicitly specify the type annotation), and mix of those. For instance, we can aim at the following syntax:

    • #{a} - for Buildable a => a
    • #s{a} - for Show a => a
    • #l{"a"} - for Builder (handles string literals, sometimes it's convenient to extracts parts of text to separate variables)
    • #n{5} - for Integer (handles integer numbers)
    • #f{3.2} - for Scientific (handles fractions)
    • Allow custom behaviour specifiers via a typeclass. I.e. the behavior of those s, l and others should not be hardcoded, but rather be specified via typeclass instances - not everyone is using Buildables. I think this point requires a separate discussion.
Regarding custom behaviours One obvious solution would be to write ```hs class InterpolationBehaviour (specifier :: Symbol) a where build :: a -> Builder instance Show a => InterpolationBehaviour "s" a where build = build . show instance (a ~ Builder) => InterpolationBehaviour "l" a where build = id ... ``` however this way we would encounter an issue - the user can't control the instances passing, so if some user's library defines a custom specifier, then the user won't be able to define another custom specifier with the same name in the projects that use this library. To overcome this, we better use datatypes as specifiers rather than type-level strings. ```hs class InterpolationBehaviour (specifier :: SpecifierKind) a where data Interpolator_s :: SpecifierKind instance Show a => InterpolationBehaviour Interpolator_s a where build = build . show ``` and `#s{abc}` should use an instance for whatever `Interpolator_s` is available in scope. This way the user can easily replace one or several specifiers in a set, and thus configure custom behaviors in a relatively flexible manner. ---
  1. Think about the best interpolation syntax. #{a}? {a}? 🐱{a}?

  2. We need a quasi-quoter that would print the text as-is, without interpolation, just in case.

  3. What should we return? I guess just FromBuilder r => r, like fmt function does. But probably we should parameterize this too so that users can e.g. get an exact Text without ugly (... :: Text). How - this is an open question.

  4. If we implement all this, maybe put it in a separate library?

  5. Should we export i from Universum module by default? This name can cause a lot of collisions. Probably we better try to find a slightly more verbose name for this quasy-quoter.

Heimdell commented 2 years ago

I'd do that like this: {a} or #{a} will use Buildable a, if the constraint is in scope, otherwise use Show a.

Other manipulators I'd like to add as functions: #{s a}, #{l a}, #{n 5}, #{f 3.2}, which would allow one to "extend the quoter" easily by writing other functions into Builder or Text.

I'd also provide a special [txt|...] interpolator that fixes the type to Text.

I think, {a} is a good syntax. It separates, but it doesn't scratch the eye like #{} does.

dcastro commented 2 years ago

In light of the recent release of the awesome nyan-interpolation, should this issue be closed? :smile:

Martoon-00 commented 2 years ago

Right :smile_cat:

Here is nyan-interpolation library.


Eventually I don't re-export it from universum because there are two variations of this interpolation library - full-fledged one which depends on the heavyweight haskell-src-exts package, and a lightweight one without that dependency but which can interpolate only variables, not arbitrary expressions.

In most cases, users will want the full version, but we cannot afford putting it to the prelude-like library.

Morley is an example of repository that preferred the lightweight interpolation.