user2879704
user2879704

Reputation:

Websocket example in haskell uses special characters as functions

> type Client = (Text, WS.Connection)
The state kept on the server is simply a list of connected clients. We've added
an alias and some utility functions, so it will be easier to extend this state
later on.
> type ServerState = [Client]
Check if a user already exists (based on username):
> clientExists :: Client -> ServerState -> Bool
> clientExists client = any ((== fst client) . fst)
Remove a client:
> removeClient :: Client -> ServerState -> ServerState
> removeClient client = filter ((/= fst client) . fst)

This is a literal haskell code taken from websockets. I don't understand how does clientExists function works,

clientExists client = any ((== fst client) . fst)

This function is invoked as,

clientExists client clients

So, how does the function refer the second argument clients? and what does . operator do?

And again at removeClient, what does the operator `/=' stand for?

Upvotes: 1

Views: 102

Answers (2)

bheklilr
bheklilr

Reputation: 54078

The . operator is the function composition operator, it's defined as

f . g = \x -> f (g x)

The /= operator is "not equals", usually written as != in other languages.

The clientExists function has two arguments, but the second one is left off since it's redundant. It could have been written as

clientExists client clients = all ((== fst client) . fst) clients

But Haskell allows you to drop the last argument in situations like this. The any function has the type (a -> Bool) -> [a] -> Bool, and the function any ((== fst client) . fst) has the type [a] -> Bool. This is saying that the function clientExists client is the same function as any ((== fst client) . fst).

Another way to think of it is that Haskell does not have multi-argument functions, only single argument functions that return new functions. This is because -> is right associative, so a type signature like

a -> b -> c -> d

Can be written as

a -> (b -> (c -> d))

without changing its meaning. With the second type signature it's more clear that you have a function that when given an a, returns a function of type b -> (c -> d). If it's next given a b, it returns a function of type c -> d. Finally, if this is given a c it just returns a d. Since function application in Haskell is so cheap (just a space), this is transparent, but it comes in handy. For example, it means that you can write code like

incrementAll = map (+1)

or

onlyPassingStudents = filter ((>= 70) . grade)

In both of these cases I've also used operator sections, where you can supply either argument to an operator, and so long as its wrapped in parens it works. Internally it looks more like

(x +) = \y -> x + y
(+ x) = \y -> y + x

Where you can swap out the + for any operator you please. If you were to expand the definition of clientExists to have all argument specified it would look more like

clientExists client clients = any (\c -> fst c == fst client) clients

This definition is exactly equivalent to the one you have, just de-sugared to what the compiler really uses internally.

Upvotes: 2

Mephy
Mephy

Reputation: 2986

When in doubt, use the GHCi interpreter to find out the types of the functions.

First off, the /= operator is the not-equal:

ghci> :t (/=)
(/=) :: Eq a => a -> a -> Bool
ghci> 5 /= 5
False
ghci> 10 /= 5
True

. is the composition of two functions. It glues two functions together, just like in mathematics:

ghci> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
ghci> :t head.tail
head.tail :: [c] -> c
ghci> (head.tail) [1, 2, 3]
2

With the basics covered, let's see how it is used in your function definition:

ghci> :t (\x -> (== fst x))
(\x-> (== fst x)) :: Eq a => (a, b) -> a -> Bool
ghci> :t (\x-> (== fst x) . fst)
(\x-> (== fst x) . fst) :: Eq b => (b, b1) -> (b, b2) -> Bool
ghci> (\x -> (== fst x) . fst) (1, "a") (1, "b")
True
ghci> (\x -> (== fst x) . fst) (1, "a") (2, "b")
False

As we can see, the (== fst x) . fst is used to take two tuples, and compare the first element each for equality. Now, this expression (let's call it fstComp) has type fstComp :: Eq b => (b, b1) -> (b, b2) -> Bool, but we are already passing it a defined tuple (client :: (Text, WS.Connection)), we curry it to (Text, b2) -> Bool.

Since we have any :: (a -> Bool) -> [a] -> Bool, we can unify the first parameter with the previous type to have an expression of type (Text, b2) -> [(Text, b2)] -> Bool. Instantiating b2 = WS.Connection we get the type of clientExists :: (Text, WS.Connection) -> [(Text, WS.Connection)] -> Bool, or using the type synonyms, clientExists :: Client -> ServerState -> Bool.

Upvotes: 1

Related Questions