TL;DR:
How can I write a function which is polymorphic in its return type? I'm working on an exercise where the task is to write a function which is capable of analyzing a String
and, depending on its contents, generate either a Vector [Int]
, Vector [Char]
or Vector [String]
.
Longer version:
Here are a few examples of how the intended function would behave:
The string "1 2\n3 4"
would generate a Vector [Int]
that's made up of two lists: [1,2]
and [3,4]
.
The string "'t' 'i' 'c'\n't' 'a' 'c'\n't' 'o' 'e'"
would generate a Vector [Char]
(i.e., made up of the lists "tic"
, "tac"
and "toe"
).
The string "\"hello\" \"world\"\n\"monad\" \"party\""
would generate a Vector [String]
(i.e., ["hello","world"]
and ["monad","party"]
).
Error-checking/exception handling is not a concern for this particular exercise. At this stage, all testing is done purely, i.e., this isn't in the realm of the IO
monad.
What I have so far:
I have a function (and new datatype) which is capable of classifying a string. I also have functions (one for each Int
, Char
and String
) which can convert the string into the necessary Vector.
My question: how can I combine these three conversion functions into a single function?
What I've tried:
(It obviously doesn't typecheck if I stuff the three conversion
functions into a single function (i.e., using a case..of
structure
to pattern match on VectorType
of the string.
I tried making a Vectorable
class and defining a separate instance for each type; I quickly realized that this approach only works if the functions' arguments vary by type. In our case, the the type of the argument doesn't vary (i.e., it's always a String
).
My code:
A few comments
Parsing: the mySplitter
object and the mySplit
function handle the parsing. It's admittedly a crude parser based on the Splitter
type and the split
function from Data.List.Split.Internals
.
Classifying: The classify
function is capable of determining the final VectorType
based on the string.
Converting: The toVectorNumber
, toVectorChar
and toVectorString
functions are able to convert a string to type Vector [Int]
, Vector [Char]
and Vector [String]
, respectively.
As a side note, I'm trying out CorePrelude
based on a recommendation from a mentor. That's why you'll see me use the generalized versions of the normal Prelude functions.
Code:
import qualified Prelude
import CorePrelude
import Data.Foldable (concat, elem, any)
import Control.Monad (mfilter)
import Text.Read (read)
import Data.Char (isAlpha, isSpace)
import Data.List.Split (split)
import Data.List.Split.Internals (Splitter(..), DelimPolicy(..), CondensePolicy(..), EndPolicy(..), Delimiter(..))
import Data.Vector ()
import qualified Data.Vector as V
data VectorType = Number | Character | TextString deriving (Show)
mySplitter :: [Char] -> Splitter Char
mySplitter elts = Splitter { delimiter = Delimiter [(`elem` elts)]
, delimPolicy = Drop
, condensePolicy = Condense
, initBlankPolicy = DropBlank
, finalBlankPolicy = DropBlank }
mySplit :: [Char]-> [Char]-> [[Char]]
mySplit delims = split (mySplitter delims)
classify :: String -> VectorType
classify xs
| '\"' `elem` cs = TextString
| hasAlpha cs = Character
| otherwise = Number
where
cs = concat $ split (mySplitter "\n") xs
hasAlpha = any isAlpha . mfilter (/=' ')
toRows :: [Char] -> [[Char]]
toRows = mySplit "\n"
toVectorChar :: [Char] -> Vector [Char]
toVectorChar = let toChar = concat . mySplit " \'"
in V.fromList . fmap (toChar) . toRows
toVectorNumber :: [Char] -> Vector [Int]
toVectorNumber = let toNumber = fmap (\x -> read x :: Int) . mySplit " "
in V.fromList . fmap toNumber . toRows
toVectorString :: [Char] -> Vector [[Char]]
toVectorString = let toString = mfilter (/= " ") . mySplit "\""
in V.fromList . fmap toString . toRows
Easy, use an sum type!
data ParsedVector = NumberVector (Vector [Int]) | CharacterVector (Vector [Char]) | TextString (Vector [String]) deriving (Show)
parse :: [Char] -> ParsedVector
parse cs = case classify cs of
Number -> NumberVector $ toVectorNumber cs
Character -> CharacterVector $ toVectorChar cs
TextString -> TextStringVector $ toVectorString cs
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With