Reputation:
> 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
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
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