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'.