Is it possisble to "and-compose" two functions?

Newbie working through The Book. Have done the exercises for chapter 3.

On exercise is to check whether an addressbook entry is in an addressbook when two string string (firstname and last anme) are given. (Excercise 4). My solution is:

isInBook :: String -> String -> AddressBook -> Boolean
isInBook first last book = not null (filter filterEntry book)
  where
    filterEntry entry = entry.firstName == first && entry.lastName == last

This works fine. However I know if I have only one condition I can streamline this to a anonymous filter function and then write the none-explicit (eta?) function definition:

isInBook1 :: String -> String -> AddressBook -> Boolean
isInBook1 first last = not null <<< filter (eq first <<< _.firstName)  -- omitting test for last!

Now my question is can I do an “and-compose” on the functions (eq first <<< _.firstName) and `(eq last <<< _.lastName) and then use this new (anonymous) function as argument to filter?

Have you tried using && to compose predicate functions?

1 Like

@natefaubion Yes, I did:

isInBook1 :: String -> String -> AddressBook -> Boolean
isInBook1 first last = not null <<< filter (eq first <<< _.firstName) && (eq last <<< _.lastName)

But I get a “Could not match type List with type Record” error.

Hi there!

The problem is that you’re not just passing one single composed predicate to filter there.

To my understanding…

filter (eq first <<< _.firstName) && (eq last <<< _.lastName)

is parsed as

(filter (eq first <<< _.firstName))
          &&
(eq last <<< _.lastName)

If seeing it like that makes sense.
That is, filter is only getting the first part of the predicate. And then you’re trying to &&-compose that entire thing with the second part of the predicate.
i.e. a bit like:

-- You're doing this
(filter pred1) && pred2

-- but what you want is
filter (pred1 && pred2)

You’ll find that the following works:

isInBookzz :: String -> String -> AddressBook -> Boolean
isInBookzz first last =
  not A.null
    <<< A.filter (eq first <<< _.first_name && eq last <<< _.last_name)

-- or
isInBookzzz :: String -> String -> AddressBook -> Boolean
isInBookzzz first last =
  A.filter (eq first <<< _.first_name && eq last <<< _.last_name)
    >>> not A.null

Note in the above that there is only one argument passed to A.filter (i.e. everything in the parentheses), but that no internal parentheses were needed for composing the predicate (i.e. && had the correct precedence there).

Here’s a different implementation you might consider:

hasBothNames :: String -> String -> SingleAddressEntry -> Boolean
hasBothNames first last =
  eq first <<< _.first_name && eq last <<< _.last_name

findEntryByNames :: String -> String -> AddressBook -> Maybe SingleAddressEntry
findEntryByNames first last = A.find (hasBothNames first last)

isInBook :: String -> String -> AddressBook -> Boolean
isInBook first last =
  maybe false (\_ -> true) <<< findEntryByNames first last

See it in action here on TryPureScript.org! :slight_smile:

4 Likes

For the curious, note that && isn’t special/magical. Rather, it is simply an infix operator for conj and performs exactly the predicate composition that you need, allowing point-free style.

See the instance of Data.HeytingAlgebra for Function.

3 Likes

Thanks heaps! I totally didn’t think of the fact that operators are normally left associative. With the parenthesis it works.

I understand this one. I even think it is more readable than the anonymous solution. I was just curious how to do it with the anonymous pattern.

Thanks again.

1 Like

Great! :smile:

Looking at it with a day later, I have no idea why I wrote:

maybe false (\_ -> true)

instead of just

isJust

in what should have been

isInBook :: String -> String -> AddressBook -> Boolean
isInBook first last = isJust <<< findEntryByNames first last

One of those things I didn’t notice at the time but is immediately obvious on a second look.
:man_shrugging:

2 Likes

It actually has nothing to do with left or right associativity in this case, but rather precedence. Function application has a higher precedence than any operator so f foo * bar will always be (f foo) * bar.

3 Likes