views:

145

answers:

3

This isn't a homework question, by the way. It got brought up in class but my teacher couldn't think of any. Thanks.

+7  A: 
let rec f x = f (f x)

This function never terminates, but it does have type 'a -> 'a.

If we only allow total functions, the question becomes more interesting. Without using evil tricks, it's not possible to write a total function of type 'a -> 'a, but evil tricks are fun so:

let f (x:'a):'a = Obj.magic 42

Obj.magic is an evil abomination of type 'a -> 'b which allows all kinds of shenanigans to circumvent the type system.

On second thought that one isn't total either because it will crash when used with boxed types.

So the real answer is: the identity function is the only total function of type 'a -> 'a.

sepp2k
`let rec f x = f x` has type scheme 'a -> 'b, which is more general than 'a -> 'a. You can get exactly 'a -> 'a by constraining the argument to be of the same type as the result, e.g. `let rec f x = f (f x)`.
Pascal Cuoq
@Pascal: Right, you are. Fixed.
sepp2k
+8  A: 

Throwing an exception can also give you an 'a -> 'a type:

# let f (x:'a) : 'a = raise (Failure "aaa");;
val f : 'a -> 'a = <fun>
Daniel Velkov
+13  A: 

How do you define the identity functions ? If you're only considering the syntax, there are different identity functions, which all have the correct type:

let f x = x
let f2 x = (fun y -> y) x
let f3 x = (fun y -> y) (fun y -> y) x
let f4 x = (fun y -> (fun y -> y) y) x
let f5 x = (fun y z -> z) x x
let f6 x = if false then x else x

There are even weirder functions:

let f7 x = if Random.bool() then x else x
let f8 x = if Sys.argv < 5 then x else x

If you restrict yourself to a pure subset of OCaml (which rules out f7 and f8), all the functions you can build verify an observational equation that ensures, in a sense, that what they compute is the identity : for all value f : 'a -> 'a, we have that f x = x

This equation does not depend on the specific function, it is uniquely determined by the type. There are several theorems (framed in different contexts) that formalize the informal idea that "a polymorphic function can't change a parameter of polymorphic type, only pass it around". See for example the paper of Philip Wadler, Theorems for free!.

The nice thing with those theorems is that they don't only apply to the 'a -> 'a case, which is not so interesting. You can get a theorem out of the ('a -> 'a -> bool) -> 'a list -> 'a list type of a sorting function, which says that its application commutes with the mapping of a monotonous function. More formally, if you have any function s with such a type, then for all types u, v, functions cmp_u : u -> u -> bool, cmp_v : v -> v -> bool, f : u -> v, and list li : u list, and if cmp_u u u' implies cmp_v (f u) (f u') (f is monotonous), you have :

map f (s cmp_u li) = s cmp_v (map f li)

This is indeed true when s is exactly a sorting function, but I find it impressive to be able to prove that it is true of any function s with the same type.

Once you allow non-termination, either by diverging (looping indefinitely, as with the let rec f x = f x function given above), or by raising exceptions, of course you can have anything : you can build a function of type 'a -> 'b, and types don't mean anything anymore. Using Obj.magic : 'a -> 'b has the same effect.

There are saner ways to lose the equivalence to identity : you could work inside a non-empty environment, with predefined values accessible from the function. Consider for example the following function :

let counter = ref 0
let f x = incr counter; x

You still that the property that for all x, f x = x : if you only consider the return value, your function still behaves as the identity. But once you consider side-effects, you're not equivalent to the (side-effect-free) identity anymore : if I know counter, I can write a separating function that returns true when given this function f, and would return false for pure identity functions.

let separate g =
  let before = !counter in
  g ();
  !counter = before + 1

If counter is hidden (for example by a module signature, or simply let f = let counter = ... in fun x -> ...), and no other function can observe it, then we again can't distinguish f and the pure identity functions. So the story is much more subtle in presence of local state.

gasche