Date night with OverloadedStrings

Posted on December 23, 2015

The OverloadedStrings extension in Haskell overloads string literals so they can act as any type that is an instance of the IsString type class. The IsString type class has only one method fromString :: IsString a => String -> a; it’s the function that does all the hard work of converting strings into your stringy type.

The IsString type class is available in Data.String without any language extensions, but with OverloadedStrings enabled, GHC implicitly inserts a fromString before every string literal in that module. This makes working with alternative text types like Text and ByteString very convenient.

I found a pretty nice use for OverloadedStrings that isn’t exactly for text types: dates. Using this extension, we can make what’s essentially a date literal. The idea is simple: make LocalTime from the time package an instance of IsString by unsafely extracting the result of a date parser to get a fromString function.

{-# LANGUAGE OverloadedStrings #-}

import Data.Maybe ( fromMaybe )
import Data.String
import qualified Data.Time as T

instance IsString T.LocalTime where
    fromString = fromMaybe (error "invalid date literal") . parseTime where
        parseTime = T.parseTimeM False T.defaultTimeLocale iso8601
        iso8601 = T.iso8601DateFormat (Just "%Y-%m-%d")

today :: T.LocalTime
today = "2015-12-23"

Of course there are some issues with this.

First, you should probably use a newtype wrapper around LocalTime instead of defining an orphan instance. Using newtypes also gives you the benefit of having different date formats for different types, by changing the definition of iso8601 for the different wrappers.

Second, if the literal is invalid, then you’ll get a runtime exception, and in Haskell those are Very Bad. With the IsString approach given here, the latter issue can’t be resolved without dependent types. There might be enough dependent type features in Haskell to do it, but I’m not really sure what can and can’t be done in Haskell yet when it comes to dependent types.

An alternative approach could be to use TemplateHaskell and a quasiquoter. I think this could be made to fail at compile time if the literal is invalid, and it avoids the gross stringiness of the IsString approach.

I guess I should go learn TemplateHaskell.