mb14
mb14

Reputation: 22636

How to chain the use of maybe argument in haskell?

I'm trying to build a string from optional arguments. For example to generate a greeting string from a title and a name This is trivial in a imperative language and would look like this

def greeting(title, name):
   s = "Hello"
   if a :
        s += "Mr"
   if b: 
        s += b

My first attempt in haskell is :

greeting :: Bool-> Maybe String -> String
greeting title name = foldl (++) "Hello" (catMaybes [title' title, name])
   where
   title' True = Just "Mr"
   title' False = Nothing

I'm sure there is a bette way to do it. First, I'm sure this foldl catMaybes combination exists somewhere but I couldn't find it. Second, folding works here because I'm using the same operation (and the same type). So what is there a better way to do it ? I was also thinking using a Writer but I'm not sure either how to do it.

Update

This is only an example (maybe bad or too simple). My question is more , how to generalize it to many arguments .

So the real problem is not just about concatening 2 strings but more how to generate letters from a template with optional sections and parameters, like you would do in Word with the mail merge tool.

You have on one a csv file with customer name, telephone number, how much is overdue etc. Some of the field could be optional. On the other side you have a template and the goal is to generate one letter per customer (row) according to the *template. The question is then how do you write this template in haskell (without the help of any templating library). In my first example the template will be "hello (Mr){name}" but in the real word the template could be in invoice, a statement letter or even a complete accounting report.

Upvotes: 2

Views: 2672

Answers (5)

cealex
cealex

Reputation: 337

I know it's an old post ... but it may be useful ...

in a pure "functional" way ...

greeting :: Maybe String -> String -> String
greeting Nothing name  = "Hello" ++ ", " ++ name
greeting (Just title) name  = "Hello" ++ ", " ++ title ++ " " ++ name ++ "!" 

how to call : 
-- greeting Nothing "John"
-- greeting Nothing "Charlotte"
-- greeting (Just "Mr.") "John"
-- greeting (Just "Miss.") "Charlotte"

Upvotes: 1

Nikita Volkov
Nikita Volkov

Reputation: 43350

Haskell is so good at abstractions, it can easily replicate the imperative patterns. What you're doing in your example is called a "builder" pattern. Writer is a monad, which wraps a pattern of accumulation or "building" of any Monoid data, and String is a Monoid.

import Control.Monad.Writer hiding (forM_)
import Data.Foldable

greeting :: Bool -> Maybe String -> String -> String
greeting mr name surname = 
  execWriter $ do
    tell $ "Hello,"
    when mr $ tell $ " Mr."
    forM_ name $ \s -> tell $ " " ++ s
    tell $ " " ++ surname
    tell $ "!"

main = do
  putStrLn $ greeting False (Just "Ray") "Charles"
  putStrLn $ greeting True Nothing "Bean"

Outputs:

Hello, Ray Charles!
Hello, Mr. Bean!

Upvotes: 2

Shoe
Shoe

Reputation: 76300

I think that your function is violating the SRP (Single responsibility principle). It is doing two things:

  • prefixing the name with Mr
  • rendering the greeting message

You can notice that the Bool and String (name) refer to the same "entity" (a person).

So let's define a person:

data Person 
    = Mister String
    | Person String
    deriving (Eq)

We can now create an instance of show that makes sense:

instance Show Person where
    show (Mister name) = "Mr. " ++ name
    show (Person name) = name

and finally we can reformulate your greeting function as:

greeting :: Maybe Person -> String
greeting (Just person) = "Hello, " ++ show person
greeting Nothing       = "Hello"

Live demo

The code is simple, readable and just few lines longer (don't be afraid of writing code). The two actions (prefixing and greeting) are separated and everything is much simpler.


If you really have to, you can trivially create a function to generate a Mister or a Person based on a boolean value:

makePerson :: Bool -> String -> Person
makePerson True  = Mister
makePerson False = Person

Live demo

Upvotes: 1

Lee
Lee

Reputation: 144206

You could avoid using a Maybe for the title and do:

greeting :: Bool-> Maybe String -> String
greeting title name = "Hello" ++ title' ++ (maybe "" id name)
  where title' = if title then "Mr" else ""

If you have a number of Maybes you could use mconcat since String is a monoid:

import Data.Monoid
import Data.Maybe

greeting :: [Maybe String] -> String
greeting l = fromJust $ mconcat ((Just "Hello"):l)

Upvotes: 1

Nikita Volkov
Nikita Volkov

Reputation: 43350

Actually Haskell is smarter than any imperative approach.

Let's imagine name has a value Nothing. Does it make sense to render something like "Hello Mr"? Probably it would make more sense to consider it as an exceptional situation, and for that we can again use Maybe. So first of all, I'd update the signature of the function to the following:

greeting :: Bool -> Maybe String -> Maybe String

Now we can look at our problem again and find out that Haskell provides multiple ways to approach it.

Hello, Monads

Maybe is a monad, so we can use the do syntax with it:

greeting mr name = do
  nameValue <- name
  return $ if mr
    then "Hello, Mr. " ++ nameValue
    else "Hello, " ++ nameValue

Hello, Functors

Maybe is also a functor, so alternatively we can use fmap:

greeting mr name = 
  if mr
    then fmap ("Hello, Mr. " ++) name
    else fmap ("Hello, " ++) name

We can also do a bit of refactoring, if we consider the signature as the following:

greeting :: Bool -> (Maybe String -> Maybe String)

i.e., as a function of one argument, which returns another function. So the implementaion:

greeting mr = 
  if mr
    then fmap ("Hello, Mr. " ++)
    else fmap ("Hello, " ++)

or

greeting True  = fmap ("Hello, Mr. " ++)
greeting False = fmap ("Hello " ++)

if you find this syntax better.

Upvotes: 10

Related Questions