Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert arbitrary type to string, without adding extra quotes to strings?

Tags:

haskell

I want to define a function which converts to strings, like the following 'toString':

toString 1 = "1"
toString True = "True"
toString "1" = "1"

Note that 'show' does not do this. By contrast it does the following:

show 1 = "1"
show True = "True"
show "1" = "\"1\""

That is, it adds extra quotes around strings. In this case I don't want to add extra quotes if I already have a string.

I'm considering using something like:

import Data.Typeable

toString a :: (Show a) => a -> String
toString a
  | typeOf a == typeOf "" = a
  | otherwise = show a

Are there any pitfalls in doing such a weird type-based conditional? Is there some built-in Haskell function that would be better to use instead?

like image 805
mherzl Avatar asked Dec 12 '16 07:12

mherzl


3 Answers

This sort of ad-hoc polymorphism is permitted through type-classes. However, they will have to be overlapping since you want a catch all case:

{-# LANGUAGE FlexibleInstances, UndecideableInstances #-}

class Print a where
  makeString :: a -> String

instance {-# OVERLAPPING #-} Print String where
  makeString s = s

instance Show a => Print a where
  makeString x = show x

Then, your function is makeString :: Print a => a -> String and it has an instance for everything that has a Show instance. To have the second instance, you need FlexibleInstances (the instance head is not of the usual form) and UndecideableInstances (since the constraint is as general as the instance head, GHC can't be sure that it won't fall into an infinite loop trying to solve these constraints).

like image 161
Alec Avatar answered Nov 07 '22 22:11

Alec


If you want something like Alec's approach without overlapping instances, you can get it with a type family.

{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, ScopedTypeVariables, UndecidableInstances, FlexibleInstances, DataKinds, ... whatever else GHC tells you it needs #-}

import Data.Text (Text, unpack)
import Data.Proxy

class Print a where
  makeString :: a -> String

data Name = NString | NText | NShow
type family Choose a where
  Choose [Char] = 'NString
  Choose Text = 'NText
  Choose _ = 'NShow

class Print' (n :: Name) a where
  makeString' :: proxy n -> a -> String

instance (Choose a ~ n, Print' n a) => Print a where
  makeString = makeString' (Proxy :: Proxy n)

instance a ~ String => Print' 'NString a where
  makeString' _ = id

instance a ~ Text => Print' 'NText a where
  makeString' _ = unpack

instance Show a => Print' 'NShow a where
  makeString' _ = show
like image 5
dfeuer Avatar answered Nov 07 '22 22:11

dfeuer


Expanding the OP solution attempt into a working one:

import Data.Typeable

toString :: (Show a, Typeable a) => a -> String
toString x = case cast x of
   Just y  -> y
   Nothing -> show x
like image 5
chi Avatar answered Nov 07 '22 22:11

chi