I am trying to build a string based on the lookup result from a Map in Haskell. This is my code so far:
import qualified Data.Map as Map
toRNA :: String -> Either Char String
toRNA [] = Right ""
toRNA (x:xs) = case getRna x of
Nothing -> Left x
Just rnaCode -> Right (rnaCode : toRNA xs)
getRna :: Char -> Maybe Char
getRna c = Map.lookup c rnaMap
rnaMap :: Map.Map Char Char
rnaMap =
Map.fromList
[ ('G', 'C')
, ('C', 'G')
, ('T', 'A')
, ('A', 'U')
]
This obviously won't work as you cannot prepend Char to Either Char String in the line of code Right (rnaCode : toRNA xs). How can you recursively call this function to build the correct string? For example: toRNA "GCT" should return Right "CGA"
The Left case here is my error case which returns the value of the first instance of a non-existent key in my rnaMap. For example: toRNA "CXL" should return Left "X"
CodePudding user response:
If you define getRna to use Either right away, toRNA becomes much simpler.
getRna :: Char -> Either Char Char
getRna c = case Map.lookup c rnaMap of
Nothing -> Left c
Just base -> Right base
-- getRna c = maybe (Left c) Right (Map.lookup c rnaMap),
-- using maybe :: b -> (a -> b) -> Maybe a -> b to abstract
-- away the case expression.
traverse acts much like map: it applies getRna to each value in the string. If that application ever produces a Left value, it immediately returns that Left value. Otherwise, it takes the resulting list of Right values and turns it into a single string wrapped by Right, i.e., [Right 'C', Right 'G'] becomes Right ['C', 'G'] == Right "CG".
toRNA :: String -> Either Char String
toRNA = traverse getRna
(In fact, traverse is just the composition of map and sequenceA, the function that "inverts" the list of Either Char Char values to an Either Char String value:
toRna = sequenceA . map getRna
or
toRna s = sequenceA (map getRna s)
)
You can also keep your original getRna and simply adapt it in the definition of toRNA:
toRNA :: String -> Either Char Sting
toRNA = traverse getRna'
where getRna' c = maybe (Left c) Right (getRna c)
If you like point-free definitions, you may get a kick out of
toRNA = traverse (flip maybe Right . Left <*> getRna)
since
flip maybe Right . Left <*> getRna
-- f <*> g == \x -> f x (g x)
== \c -> (flip maybe Right . Left) c (getRna c)
== \c -> (flip maybe Right (Left c)) (getRna c)
== \c -> (maybe (Left c) Right) (getRna c)
CodePudding user response:
I'd suggest using the fact that Either is an functor:
toRNA :: String -> Either Char String
toRNA [] = Right ""
toRNA (x:xs) = case getRna x of
Nothing -> Left x
Just rnaCode -> (rnaCode:) <$> toRNA xs
(<$> is an infix synonym of fmap)
CodePudding user response:
Questions asking "how do I do this thing elegantly" seem to draw people in :) Here's yet another option, which doesn't redefine getRna and uses the Applicative rather than (merely) the Functor instance for Either String:
import Data.Maybe (maybe)
toRNA :: String -> Either Char String
toRNA [] = Right ""
toRNA (x:xs) = (:) <$> maybe (Left x) Right (getRna x) <*> toRNA xs
I think chepner's way is best, honestly, but I spent a minute working this out and figured I might as well post it; this f <$> a <*> b thing (which is essentially f a b, except everything involved is wrapped in an Applicative) shows up fairly often, especially when using parser combinators, and I find it neat.
