I have asked a question why my attempt to do a function chaining did not work: Make a function return itself after doing some work, the answer was: for a function to return itself you need recursive types enabled with -rectypes. This confused me. Why is this feature hidden behind a compiler flag? There must be a good reason not to enable it by default. So my question is: what are the tradeoffs when using this flag? Should I avoid it or can I safely use it for all my code and it is not enabled for compatibility with old code only?
What are the tradeoffs of using -rectypes?
297 Views Asked by exebook At
2
There are 2 best solutions below
0
On
If this option is disabled by default it's because it will make the OCaml compiler accept a lot of code that should usually be considered as wrong, giving them weird types, producing even weirder errors and error messages. Overall, it would more often confuse than be helpful. Which is why you need to explicitly enable it.
The documentation gives a good example of such a situation:
let included l1 l2 =
let rec mem x = function
| [] -> false
| hd::tail -> (tail=x) || (mem x hd) (* an error on purpose: hd and tail inverted *)
in
List.for_all (fun x -> mem x l2) l1
In this case, without the flag, the compiler will reject this, as it should; but without, it will be accepted, and you will have the following oddities:
# included [] [];;
- : bool = true
# included [1] [];;
Error: This expression has type int but an expression was expected of type
('a t as 'a) t
#
In brief, recursive types are both confusing for many users and often avoidable for the expert users that might need them. This is why the
-rectypesoption has been never been the default since its introduction in OCaml 2.03 .In term of errors, consider for instance, this erroneous implementation of the
appendfunctionwithout
-rectypesthe type system catches the error:However, with
-rectypesenabled,quasi_appendis a valid function of typeval quasi_append : ('a list as 'a) list -> 'a list -> 'a listthat only works on list of ... list of ... empty lists:Thus
-rectypesintroduces complex types even in code written by beginner (in the previous example the mistake was a single::replaced by@).Moreover, even for expert users, explicit recursive types can often be avoided. For instance, the function in your previous question can be written without recursive types by adding one recursive type definition
Here, we use the fact that
fixis a recursive algebraic type to hide the recursion behind theFixconstructor. Then, we only have to box the log function in aFixconstructor in the log function to avoid having an explicit recursive type:We can even define a helper application operator which unbox the function before applying it for us
Finally, we can use the
logby inserting$between each applicationUsing those kind of techniques, recursive types can be often avoided by expert users. Moreover, there is one exception for the need to use
-rectypes: recursive objects and polymorphic variants . Both recursive objects and polymorphic variants often require recursive types, but it is easy to identify code that involves either of them. Thus recursive types are enabled by default for polymorphic variants types and object types. For instance, one may write an object with alogmethod that returns the whole objectthe type of
ois then< log : int -> 'a > as 'a.Consequently, the choice was made to left
-rectypesfor expert users that are certain that they need the added complexity (the proof assistantCoqrelies on-rectypesfor instance).¹ The unboxed annotation tells the compiler that it can store the function directly in memory since there is only one constructor. Thus the type constructor only exists when typechecking.