This isn't a homework question, by the way. It got brought up in class but my teacher couldn't think of any. Thanks.
The way -> is defined, a function always takes one argument and returns only one element. A function with multiple parameters can be translated into a sequence of unary functions.
The type 'a is a type variable, and stands for any given type. The reason why sort can apply to lists of any type is that the comparisons (=, <=, etc.) are polymorphic in OCaml: they operate between any two values of the same type. This makes sort itself polymorphic over all list types.
Anonymous Functions An anonymous function is a function that is declared without being named. These can be declared using the fun keyword, as shown here. (fun x -> x + 1);; - : int -> int = <fun> OCaml.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With