2

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.

3
  • You haven't specified what options is. Commented May 23, 2014 at 21:38
  • Does greeting True Nothing make sense as an invocation (it would produce Hello Mr, assuming you mean to add a space between the two words)? Commented May 23, 2014 at 21:58
  • @Jeffrey: It doesn't but it doesn't really matter. It's just an example. Maybe I should have choosen a more abstract example. Commented May 24, 2014 at 7:24

5 Answers 5

10

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.

Sign up to request clarification or add additional context in comments.

4 Comments

"Actually Haskell is smarter than any imperative approach." He says it how it is! (Although Haskell is the finest imperative language.)
Thanks for details answer. However, in each of your solution you use "Hello" twice, which I'm trying to avoid. Also, your functions doesn't return "Hello" but "" if name is not provided. My question was more about how to generalize that to n-argument instead of reducing it to one.
@mb14 First of all, the examples I posted never return an empty string, they return Nothing in case there is no name. Secondly don't take the DRY principle to extremes, because in too simple cases it takes more code and obfuscates the otherwise evident solution. However I think I finally got what you were trying to ask so please see my new answer.
My question was obviously not clear enough. I updated my question. And yes your new answer is exactly what I needed.
2

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! 

1 Comment

that's the forM I was looking for. Thank you very much.
1

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) 

Comments

1

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

5 Comments

I agree about the SRP violation. However, this is kind of irrelevant here. I'm not trying to solve the exact problem in the question. It was just a "simple" example to clarify a more generic question. Moreover, your solution is using "Hello" twice, which is violationg the DRY principle (which I think is more important that SRP)
@mb14, the alternatives to overcome the 5 letters duplication much longer and reduce the simplicity of the solution. If the text was bigger I would definitely care about it and either create a hello = ("Hello"++) or move the case to test the Maybe value onwards.
@jeffrey: My question maybe wasn't clear enough. I was more thinking of a complete template system that just adding 2 Maybe strings. If you can do it for 2, you can do it for n can't you ? See my update
@jeffrey : I downvoted because I felt refactoring a made-up example wasn't a constructive answer. See my initial comment.
@mb14, why didn't you downvote this one as well?
1

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" 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.