Haskell a better way a replacing a character in a string

Viewed 128

I made this function

replace :: Char -> Char -> [Char] -> [Char]
replace _ _ [] = []
replace a b (c:r)
    | a == c = b : replace a b r
    | otherwise = c : replace a b r

and was looking for a better way to write it when i found this :

replace :: Char -> Char -> [Char] -> [Char]
replace a b  = map $ \c -> if c == a then b else c

There not even the third argument written, and i don't understand the $ and the \c symbols, but it work and I want to know what is happening here

2 Answers

The $ operator applies a function to an argument. We can rewrite your example as

replace :: Char -> Char -> [Char] -> [Char]
replace a b  = map (\c -> if c == a then b else c)

Pragmatically, Haskellers often use $ to avoid parentheses. It's rarely used for other reasons.

About the \c -> ...: this is an anonymous function, also called a "lambda". It stands for the function taking c as argument and returning the ... part. In your case, the function takes c and checks if it's equal to a, in which case it returns b, otherwise it returns c. We could rewrite the code without the lambda as follows:

replace :: Char -> Char -> [Char] -> [Char]
replace a b  = map myFun
   where
   myFun :: Char -> Char
   myFun = \c -> if c == a then b else c

or, moving the argument c to the left of =, as follows:

replace :: Char -> Char -> [Char] -> [Char]
replace a b  = map myFun
   where
   myFun :: Char -> Char
   myFun c = if c == a then b else c

About the "missing third argument": the type Char -> Char -> [Char] -> [Char] can be read in multiple ways:

  • the type of functions taking one argument (Char) and returning a function (Char -> [Char] -> [Char])
  • the type of functions taking two arguments (Char and Char) and returning a function ([Char] -> [Char])
  • the type of functions taking three arguments (Char, Char, and [Char]) and returning a list ([Char])

All these three interpretations are compatible, thanks to currying. Indeed, the "two arguments" function

foo :: A -> B -> B
foo x y = y

and the function

foo :: A -> B -> B
foo x = id              -- id is the identity function B -> B

are the same.

In your example, if you want, you can add the missing argument to both sides of = as follows:

replace :: Char -> Char -> [Char] -> [Char]
replace a b xs = map myFun xs
   where
   myFun :: Char -> Char
   myFun c = if c == a then b else c

In this expanded code, you can see that map myFun xs uses map (library function) to apply myFun to all the elements of list xs, and return the list of all the results. This effectively achieves the substitution you want.

However, without adding the third argument,

replace :: Char -> Char -> [Char] -> [Char]
replace a b = map myFun
   where ...

we can still interpret map myFun as transforming the function myFun :: Char -> Char into a function [Char] -> [Char]. The latter is indeed the return type of replace if we interpret that as a "two arguments" function. That is, replace 'a' 'b' is the function [Char] -> [Char] which takes a string and replaces every 'a' in it with a 'b'.

There not even the third argument written. (…)

In Haskell, all functions have only one argument. Indeed Char -> Char -> [Char] -> [Char] is short for Char -> (Char -> ([Char] -> [Char])), it is thus a function that takes a Char and then returns another function. If we apply that function with another Char, it returns a function of type [Char] -> [Char] that will map a list of Chars to a list of Chars, and finally if we call that function with a list of Chars we get a list of Chars.

This thus means that if there are two parameters in the head of the function, that map $ \c -> if c == a then b else c should return a function, and that is the case.

(…) I don't understand the $ (…) symbols.

The ($) :: (a -> b) -> a -> b function is defined as:

infixr 0 $

($) :: (a -> b) -> a -> b
($) f x = f x

it is thus function application. The reason why that is used is because it has a precedence of 0, this thus means that a $ b x for example will be evaluated as a (b x), whereas a b x, will be evaluated as (a b) x. It thus means that the function is equivalent to map (\c -> if c == a then b else c).

I don't understand (…) the \c symbols

That is a lambda expression. It is a function that takes as input a variable c and maps this on the part at the right of the arrow (->). Here it thus means that it maps a Character c to b if c == a; and otherwise it returns c.

Related