Search code examples
haskell

Trying to understand Data.Text all function


I was playing around with the Data.Text all function, doing some trivial examples and combining them when I found that the evaluation of this trivial piece of code in GHCI is evaluating to True, the question is why?

import qualified Data.Text  as T
T.all C.isSymbol (T.filter C.isNumber (T.pack "asad"))
GHCI => True

I am baffled because the evaluation of:

(T.filter C.isNumber (T.pack "asad"))
GHCI => ""

Is this a bug? or is by design?


Solution

  • From high school math, you may remember learning about sums and products like the ones drawn on this page and that one. You might recall learning that when a sum doesn't have any terms, we say it's 0, and when a product doesn't have any terms, we say it's 1. But did you ever learn why we pick those definitions?

    In Haskell, we can see that the base library reflects those facts in its definitions of sum and product:

    > sum []
    0
    > product []
    1
    

    One reason these choices are nice is the following property: if we have a sum with a bunch of terms, we can split up the terms arbitrarily into two groups, sum them separately, and add the results together. Using a Haskell-inspired syntax, we might express a similar property like this:

    forall xs ys, sum xs + sum ys = sum (xs ++ ys)
    

    One interesting thing about this property is that it forces our hand in how we define the sum of an empty list -- it has to be 0!

    sum [] + sum ys = sum ([] ++ ys) = sum ys
    

    Now just subtract sum ys from both sides. A similar property for products,

    forall xs ys, product xs * product ys = product (xs ++ ys)
    

    forces us to choose 1:

    product [] * product ys = product ([] ++ ys) = product ys
    

    Is there a similar property for all? Yes! If all the elements of one list satisfy a predicate, and all the elements of another list satisfy that same predicate, then when you concatenate those lists, all the elements still satisfy that predicate.

    forall p xs ys, all p xs && all p ys = all p (xs ++ ys)
    

    By similar reasoning to that done above, we can get a pretty good idea for what all should do on empty lists:

    forall p ys, all p [] && all p ys = all p ([] ++ ys) = all p ys
    

    The simplest way to validate the equation all p [] && X = X is to choose all p [] = True -- and this is exactly what's been done.

    If you'd like to practice this kind thinking, can you think of a property like this for any? What should any p [] do to validate that property? Suppose I define:

    lcms :: Integral a => [a] -> a
    lcms (x:xs) = lcm x (lcms xs)
    

    where

    -- lcm x y is the smallest positive integer that both x and y divide.
    lcm :: Integral a => a -> a -> a 
    

    What should I pick to be the answer for lcms []?