Restricting string literals to Text only
Asked Answered
S

2

43

I'm aware that the OverloadedStrings language pragma wraps an implicit fromString around all string literals. What I'd like to do is not actually overload strings, but merely change their meaning so that they are always turned into Text, and therefore, using a string literal as a list of characters should result in a type error.

It appears to be impossible to import the IsString class without also importing the String instance for that class. Does ghc provide some way for me to restrict string literals to Text only?

Sardine answered 19/7, 2012 at 1:31 Comment(3)
To be clear: manually wrapping all string literals in a function asText :: Text -> Text; asText = id is not an acceptable solution.Sardine
Note that unless you have atypically gargantuan literals, then Text might not be any more efficient than String. Text really shines when you are getting strings from IO.Bifocal
See this haskell-cafe threadToed
B
51

It's a little bit of overkill, but one solution is to combine OverloadedStrings and RebindableSyntax. The RebindableSyntax extension causes all the implicit function calls that Haskell syntax uses to refer to whatever functions are in scope; for instance, integer literals use any fromIntegral, not necessarily Prelude.fromIntegral. As a side effect, Prelude is no longer implicitly imported, so you have to do that manually. As long as you do import it, there shouldn't be any issues with syntax using the wrong function implicitly (I think—I haven't actually used this technique). When combined with OverloadedStrings, this causes "foo" to be transformed into fromString "foo" for whatever fromString's in scope, not necessarily Data.String.fromString "foo". So making fromString synonymous with pack will do what you want. A complete example:

{-# LANGUAGE OverloadedStrings, RebindableSyntax #-}
import Prelude

import qualified Data.Text    as T
import qualified Data.Text.IO as T

fromString :: String -> T.Text
fromString = T.pack

main :: IO ()
main = T.putStrLn "Hello, world!"

This works fine, and changing main to main = putStrLn "Hello, world!" produces the desired error:

TestStrings.hs:11:17:
    Couldn't match expected type `String' with actual type `T.Text'
    Expected type: [Char] -> String
      Actual type: String -> T.Text
    In the first argument of `putStrLn', namely `"Hello, world!"'
    In the expression: putStrLn "Hello, world!"

Commenting out the definition of fromString causes a different error:

TestStrings.hs:11:19:
    Not in scope: `fromString'
    Perhaps you meant `showString' (imported from Prelude)

If you want it to work with both strict and lazy text, you could define your own IsString type class, and make both of them instances; the class doesn't have to be called IsString, just so long as it has a fromString method.

Also, a word of warning: the section of the GHC manual on RebindableSyntax doesn't mention the fromString function, and the section on OverloadedStrings doesn't mention RebindableSyntax. There's no reason this shouldn't work, but I think that means that this solution technically relies on undocumented behavior.

Bicycle answered 19/7, 2012 at 2:42 Comment(4)
Interesting, I hadn't thought of that. It is a bit overkill, but the reason I have for doing this is the same flavor of overkill, so it's all good.Sardine
Does the T.packing happen at compile time or runtime?Belligerency
@CetinSert: The core (with -O or -O2) is main2 = Data.Text.unpackCString# "Hello, world!", so at least it doesn't seem to go via String at runtime.Earthy
The missing documentation is a bug, feel free to improve the documentation!Vietcong
V
1

Not a way to achieve this now, but maybe eventually, is the instance force as proposed in a GHC feature proposal under discussion, where you’d say

instance force IsString Text

in your module. This is one of the main motivating examples for the proposal.

Vietcong answered 26/1, 2017 at 17:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.