8

Consider

(a->a) -> [a] -> Bool

Is there any meaningful definition for this signature? That is, a definition that not simply ignores the argument?

x -> [a] -> Bool

It seems there are many such signatures that can be ruled out immediately.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
false
  • 10,264
  • 13
  • 101
  • 209
  • 2
    `[]` is not a valid type. – MathematicalOrchid Nov 18 '14 at 13:23
  • 2
    if you ignore the first argument you have `null` and co - including the first argument I cannot come up with any meaningful no - the problem seems to call for "Theorems for free" ;) – Random Dev Nov 18 '14 at 13:28
  • What is your definition of 'meaningful'? There are certainly functions of these types, but they will ignore one or more arguments. There are other signatures that are truly meaningless, like `f :: a -> b`. There are no (terminating) functions of this type. – augustss Nov 18 '14 at 16:34
  • 1
    @augustss: (I do understand the usefulness of `const` ignoring its argument) w.r.t meaningful: What intention a programmer would express with it. So it seems only some `bot` like "meaning" could be associated. BTW, how can your `f` be called in a program to become non-terminating? It needs someone to call it who needs it, who then must be of that type. It works on the toplevel with `let f x=f x;in f 1` though. – false Nov 18 '14 at 17:05
  • @false You still didn't define what you mean by meaningful. :) I think you answered your own question about `f`. – augustss Nov 18 '14 at 17:23
  • @augustss: I have a definition in my question: Anything that does not simply ignore the argument. This is quite a meager meaning, but better than none. And you somehow brought up non-terminating functions. However, I still fail to see how such a non-terminating function of type `a->a` or even `a->b` can be "smuggled" through given signature to make the function non-terminating – false Nov 19 '14 at 12:25
  • 2
    @false Your definition of meaningful would rule out `const`. But I think you can refine your definition of meaningful to something that allows `const`. A function meaningful if ignored arguments are not restricted unnecessarily in the signature, so `(a->a) -> b -> b` has no meaningful definition, because the first argument must be ignored, but is restricted. Whereas `a -> b -> b` is OK. If this is the definition of meaningful then `(a->a) -> [a] -> Bool` has no meaningful definitions. – augustss Nov 19 '14 at 15:37

2 Answers2

10

Carsten König suggested in a comment to use the free theorem. Let's try that.

Prime the cannon

We start by generating the free theorem corresponding to the type (a->a) -> [a] -> Bool. This is a property that every function with that type must satisfy, as established by the famous Wadler's paper Theorems for free!.

forall t1,t2 in TYPES, R in REL(t1,t2).
 forall p :: t1 -> t1.
  forall q :: t2 -> t2.
   (forall (x, y) in R. (p x, q y) in R)
   ==> (forall (z, v) in lift{[]}(R). f_{t1} p z = f_{t2} q v)

lift{[]}(R)
  = {([], [])}
  u {(x : xs, y : ys) | ((x, y) in R) && ((xs, ys) in lift{[]}(R))}

An example

To better understand the theorem above, let's run over a concrete example. To use the theorem, we need to take any two types t1,t2, so we can pick t1=Bool and t2=Int.

Then we need to choose a function p :: Bool -> Bool (say p=not), and a function q :: Int -> Int (say q = \x -> 1-x).

Now, we need to define a relation R between Bools and Ints. Let's take the standard boolean <->integer correspondence, i.e.:

R = {(False,0),(True,1)}

(the above is a one-one correspondence, but it does not have to be, in general).

Now we need to check that (forall (x, y) in R. (p x, q y) in R). We only have two cases to check for (x,y) in R:

Case (x,y) = (False,0): we verify that (not False, 1-0) = (True, 1) in R   (ok!)
Case (x,y) = (True ,1): we verify that (not True , 1-1) = (False,0) in R   (ok!)

So far so good. Now we need to "lift" the relation so to work on lists: e.g.

[True,False,False,False] is in relation with [1,0,0,0]

This extended relation is the one named lift{[]}(R) above.

Finally, the theorem states that, for any function f :: (a->a) -> [a] -> Bool we must have

f_Bool not [True,False,False,False] = f_Int (\x->1-x) [1,0,0,0]

where above f_Bool simply makes it explicit that f is used in the specialised case in which a=Bool.

The power of this lies in that we do not know what the code of f actually is. We are deducing what f must satisfy by only looking at its polymorphic type.

Since we get types from type inference, and we can turn types into theorems, we really get "theorems for free!".

Back to the original goal

We want to prove that f does not use its first argument, and that it does not care about its second list argument, either, except for its length.

For this, take R be the universally true relation. Then, lift{[]}(R) is a relation which relates two lists iff they have the same length.

The theorem then implies:

forall t1,t2 in TYPES.
 forall p :: t1 -> t1.
  forall q :: t2 -> t2.
   forall z :: [t1].
    forall v :: [t2].
     length z = length v ==> f_{t1} p z = f_{t2} q v

Hence, f ignores the first argument and only cares about the length of the second one.

QED

chi
  • 111,837
  • 3
  • 133
  • 218
7

You can't do anything interesting with x on it's own.

You can do stuff with [x]; for example, you can count how many nodes are in the list. So, for example,

foo :: (a -> a) -> [a] -> Bool
foo _ [] = True
foo _ (_:_) = False

bar :: x -> [a] -> Bool
bar _ [] = True
bar _ (_:_) = False

If you have an x and a function that turns an x into something else, you can do interesting stuff:

big :: (x -> Int) -> x -> Bool
big f n = if f n > 10 then True else False

If x belongs to some type class, then you can use all the methods of that class on it. (This is really a special-case of the previous one.)

double :: Num x => x -> x
double = (2*)

On the other hand, there are plenty of type signatures for which no valid functions exist:

magic :: x -> y
magic = -- erm... good luck with that!

I read somewhere that the type signatures involving only variables for which a real function exists are exactly the logical theorems that are true. (I don't know the name for this property, but it's quite interesting.)

f1 :: (x -> y) -> x -> y
-- Given that X implies Y, and given that X is true, then Y is true.
-- Well, duh.

f2 :: Either (x -> y) (x -> z) -> x -> Either y z
-- Given that X implies Y or X implies Z, and given X, then either Y or Z is true.
-- Again, duh.

f3 :: x -> y
-- Given that X is true, then any Y is true.
-- Erm, no. Just... no.
MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • Discussion [moved to chat](http://chat.stackoverflow.com/rooms/65212/discussion-on-answer-by-mathematicalorchid-type-signatures-that-never-make-sense). – George Stocker Nov 19 '14 at 13:10