7

I try to find a solution on an exercise of my own, with the following requirements :

  • We need to move an object against a given sequence.
  • A sequence is composed of actions.

Here are the possible actions : F, L, R

  • F : move Forward
  • L : rotate 90° to the left
  • R : rotate 90° to the right

A sequence is then represented by a string, like this :

"FFLRLFF"

I want to parse the above sequence (and handle errors) and then bind each action to a function, like this :

parseAction :: Char -> Either String (a -> a)
parseAction 'F' = Right moveForward
parseAction 'L' = Right turnLeft
parseAction 'R' = Right turnRight
parseAction s   = Left ("Unkown action : " ++ [s])

-- Implementation omitted
moveForward :: a -> a
turnLeft :: a -> a
turnRight :: a -> a

Now what I want is something with the following signature :

parseSequence :: String -> Either String [(a -> a)]

I want to parse a full sequence by using many times parseAction function, and fails when that one returns Left. I'm stuck on how can I implement this function.

Do you have any ideas ?

duplode
  • 33,731
  • 7
  • 79
  • 150
Maxime
  • 570
  • 3
  • 18
  • 1
    I was going to suggest Hoogle, but I found it surprisingly hard to get Hoogle to give me the right answer. I started with `(Char -> Either String (a -> a)) -> (String -> Either String [(a -> a)])`, but that gave results; getting the right answer required both the insight that I could replace `a -> a` with the more-polymorphic `a`, and that I could replace `Either String` with the more-polymorphic `f`. Only then did it suggest `mapM`. Oh well, at least I didn't have to have the insight that `Char` and `String` could be replaced with the more-polymorphic `b` and `[b]`. – Daniel Wagner Sep 11 '15 at 21:00
  • Isn't `id` the only valid functions of type `a -> a`? (Ignoring errors, undefined, unsafe side effects, etc) – user253751 Sep 12 '15 at 01:16

3 Answers3

11

This looks like

mapM :: Monad m => (a -> m b) -> [a] -> m [b]

where

a ~ Char
b ~ (a -> a)
m ~ (Either String)

So an implementation is simply:

parseSequence :: String -> Either String [a -> a]
parseSequence = mapM parseAction

As an aside, note that your parseAction doesn't really want to be using type (a -> a), which must work for any type a, chosen by the person calling the function. You instead want it to use type (Location -> Location), where Location is whatever type you're using to represent the location of the object you're moving.

Equivalently to mapM, you can (as suggested by duplode) instead use traverse, which is slightly more general. In recent versions of GHC traverse is in Prelude; in older versions you may have to import it from Data.Traversable to use it.

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • 1
    Indeed, the parseAction is actually working on lawn mower :), but for the purpose of explanation, I didn't explain it. – Maxime Sep 11 '15 at 20:04
7

Note: For extra clarity, I am going to use Thing -> Thing rather than a -> a.

You need traverse:

traverse parseAction
  :: Traversable t => t Char -> Either String (t (Thing -> Thing))

In your case, t is [], and so, t Char is String. traverse parseAction will walk across the String, generating one action for each Char and collecting the results. traverse uses the Applicative instance of Either to handle Lefts and Rights, stopping at the first Left.

P.S.: mapM in amalloy's answer is, in this case, equivalent to traverse. The only difference between them is that traverse is more general, as it only requires Applicative (rather than Monad) from the functor you use while traversing.

duplode
  • 33,731
  • 7
  • 79
  • 150
  • So does mapM handle Left aswell ? – Maxime Sep 11 '15 at 20:17
  • 1
    @MaximeB. Yes, it does. In practice, there is no difference between `mapM` and `traverse` other than the type signature. (It might be said that `mapM` only exists because, back when it was introduced, `Applicative` and `Traversable` didn't exist yet. It worked just the same because you can make every `Monad` an `Applicative`. Nowadays that is reflected in `Applicative` being a superclass of `Monad`.) – duplode Sep 11 '15 at 20:32
2

If you map parseAction over your source string you get part of the way. In GHCi:

> :type map parseAction "FFRRF"
[Either String (a->a)]

So now you can fold this into a single Either value

validActions = foldr f (Right [])
   where
      f (Left str) _ = Left str
      f (Right x) (Right xs) = Right (x:xs)
      f (Right x) (Left str) = error "Can't happen"

However this has an annoying "error" case. So to get rid of that:

import Data.Either

validActions vs = if null ls then Right rs else Left $ head ls
   where (ls, rs) = partitionEithers vs
Paul Johnson
  • 17,438
  • 3
  • 42
  • 59