Home > Blockchain >  Recursion on a haskell function with an Either return type
Recursion on a haskell function with an Either return type

Time:12-23

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.

  • Related