Haskell - the impossible is possible?
It is known that the task of determining whether a certain function is true for
To begin with, we will set our type of natural numbers, almost literally following their usual mathematical definition (why this will be needed in the future):
In other words, a positive integer is either zero or some positive integer increased by one (
Also, for convenience, we define the basic operations (addition, multiplication, conversion from
So far, it seems, nothing special in terms of differences of this type from the usual
Recall that we want a function of the form
In fact, it is almost obvious that if the desired number exists, this function will return the correct answer. Indeed, if
However, this function has a name
Interestingly, a fully working solution can be made based on the function above
At first glance, it is not clear how this will work, and whether it will. Let's check with simple examples:
That is, the function
In fact, if you look, then everything is simple. Having received a
A function
Now you can explain in simple words why and how everything works for always-ending functions. After all, if
In essentially the same way, you can create functions like
PS: this article is a little supplemented by a “retelling” of Searchable Data Types , therefore it is not marked as a translation.
Integer -> Bool
at least one number is computationally insoluble. However, something that at first glance seems to be just such an oracle (namely, a function (Integer -> Bool) -> Maybe Integer
) will be described in this article. To begin with, we will set our type of natural numbers, almost literally following their usual mathematical definition (why this will be needed in the future):
dataNat = Zero | SuccNatderiving (Eq, Ord, Show)
In other words, a positive integer is either zero or some positive integer increased by one (
Succ
from the word successor). Also, for convenience, we define the basic operations (addition, multiplication, conversion from
Integer
) on numbers in this representation:instanceNumNatwhereZero + y = y
Succ x + y = Succ (x + y)
Zero * y = ZeroSucc x * y = y + (x * y)
fromInteger 0 = Zero
fromInteger n = Succ (fromInteger (n-1))
So far, it seems, nothing special in terms of differences of this type from the usual
Integer
. Recall that we want a function of the form
(Nat -> Bool) -> Maybe Nat
, the result of which will be the number on which the input function returns True
, or Nothing
if there is no such number. A first approximation may be, for example, a similar function:lyingSearch :: (Nat -> Bool) -> NatlyingSearch f | f Zero = Zero
| otherwise = Succ (lyingSearch (f . Succ))
In fact, it is almost obvious that if the desired number exists, this function will return the correct answer. Indeed, if
f Zero == True
, then the return value will be Zero
- right. Otherwise, the function will return x+1
where x
- the value at which the function f(x+1)
is true - is also true. However, this function has a name
lyingSearch
in vain : in the case when the required number is not there, the function will go to recursion at every step and will return, in fact, infinity: Succ (Succ (Succ (...
where nesting will never end. Because of laziness, Haskell
this is a normal situation. But infinity is not the desired answer - therefore, in this case, the function "lies". Interestingly, a fully working solution can be made based on the function above
lyingSearch
. Consider a function search
defined as follows:search f | f possibleMatch = Just possibleMatch
| otherwise = Nothingwhere
possibleMatch = lyingSearch f
At first glance, it is not clear how this will work, and whether it will. Let's check with simple examples:
ghci> search (\x -> x*x == 16) -- такое работало и для lyingSearchJust (Succ (Succ (Succ (SuccZero))))
ghci> search (\x -> x*x == 15)
Nothing
That is, the function
search
correctly determined that there is no natural number with a square equal to fifteen. In fact, if you look, then everything is simple. Having received a
lyingSearch
possible result (which is always a valid type value Nat
), we simply pass it to the input of the function f
and check the return value. If the desired number exists, then (as already explained earlier) possibleMatch
- this is the number, and therefore the check will be successful. Otherwise, because f
completed for any input value, we will receive False
and return Nothing
. A function
search
really works for any predicate (function Nat->Bool
), and terminates in a finite time (of course, provided thatf
also terminates for any type value Nat
). However, the termination condition f
for any argument passed is very strong, and it is it that strongly limits the set of permissible predicates: for example, when there f x = x*x + 1 == x
is an infinite loop. It would seem that this is not so, because such a function ends for any number? It turns out that for anyone other than the already mentioned futility: to the left and right of the equal sign will be infinitely nested Succ (Succ (Succ (...
, and accordingly it will be impossible to determine whether the left and right sides are equal. It is for this reason that it is impossible to use this function to create a similar type Integer
. Now you can explain in simple words why and how everything works for always-ending functions. After all, if
f
ends at the infinity passed to it, that is, the sequence Succ (Succ (Succ (...
, which means that in any case it uses (reveals) no more than some fixed number of constructors Succ
. In essentially the same way, you can create functions like
search
for other types. Relatively simple examples are also real numbers, each represented as an endless list of numbers (see Seemingly Impossible Functional Programs ). On hackage there is a generic package infinite-search , which provides the appropriate monad and related functions. PS: this article is a little supplemented by a “retelling” of Searchable Data Types , therefore it is not marked as a translation.