So, suppose we already had this list of all possible lines:
allStrings :: [String] allStrings = ...
Given allStrings
, how can we create a different list of all possible strings?
alsoAllStrings :: [String]
Let's look at each possible line as a prefix of one character and the suffix of the line
alsoAllStrings = [ c : s
A line suffix is ββeither an empty line or a member of a list of all possible lines.
| s <- "" : allStrings
The single character prefix is ββin 'a'
thru 'z'
or '0'
thru '9'
.
, c <- ['a'..'z'] ++ ['0'..'9'] ]
(this is using lists - we can also do the same with concatMap
:
alsoAllStrings = concatMap (\s -> map (:s) $ ['a'..'z'] ++ ['0'..'9']) $ "" : allStrings
)
Now back to the original question. How to find allStrings
?
In most languages, we could not - this is an endless list of lines, the program never ended. But since Haskell is lazy, it's cool, creating only the part of allStrings
that we actually use.
One amazing thing that this allows us to do is to define allStrings
in terms of alsoAllStrings
.
allStrings = alsoAllStrings
Or, more likely, from the point of view of himself.
allStrings = [ c : s | s <- "" : allStrings, c <- ['a'..'z'] ++ ['0'..'9'] ]
This is called core-recursive data β data that is defined on its own. Try it in ghci:
ghci> let allStrings = [ c : s | s <- "": allStrings, c <- ['a'..'z'] ++ ['0'..'9'] ] ghci> take 100 allStrings ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9","aa","ba","ca","da","ea","fa","ga","ha","ia","ja","ka","la","ma","na","oa","pa","qa","ra","sa","ta","ua","va","wa","xa","ya","za","0a","1a","2a","3a","4a","5a","6a","7a","8a","9a","ab","bb","cb","db","eb","fb","gb","hb","ib","jb","kb","lb","mb","nb","ob","pb","qb","rb","sb","tb","ub","vb","wb","xb","yb","zb","0b","1b"]
The reason it works (and not just the cycle endlessly) is because it defines a part of itself before it uses it. The first elements that we include in allStrings
are single character extensions to the empty string ""
. Thus, by the time we start repeating allStrings
elements for use as suffixes, the first pairs of allStrings
elements allStrings
already defined and available. And the more suffixes we process, the more allStrings
elements allStrings
defined and available for use as suffixes.
It is very similar to general heckellism, which determines the fibonacci number:
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
Don't be surprised if the core recursion takes some time to wrap your head. However, itβs worth trying to understand.