From: Pierre Weis <Pierre.Weis@inria.fr>
> > The rationale behind allowing the use of keywords as labels is that
> > there are lots of keywords in Caml, and many of them are good
> > candidates for labels. Not allowing them makes often choice more
> > difficult, or forces to use longer names.
>
> The problem is that we have just a few rule for the Caml syntax, and
> this violates this elementary one:
>
> ``In Caml, keywords are RESERVED identifiers''
This can be discussed indeed. Formally the rule is not broken: in the
manual labels are defined as an alphanumeric string ending by `:',
which is lexically different from any keyword. But this definition may
seem strange. Before definitively deciding on this, we can start by
the standard library.
> > This would mean:
> >
> > fun -> func
>
> Could also consider fn
I don't know why, but I don't like very much fn:, maybe because it
reminds me of another language :-)
In general, I think consonant-only labels are not very good, because
you have to mentally add the missing vowels when reading.
That's pretty subjective, though.
> > > -- Problem2: labels that spread all over the standard libraries, even
> > > when they do not add any good.
> >
> > Well, the current policy (as defined in the manual) is to put labels
> > on at least all arguments but one (with some very special exceptions
> > like printf). This is necessary to allow commuting between arguments,
> > which is also an important theme of labels. As such I would not say
> > that they do not do any good.
>
> I'm afraid I don't agree with you: commuting between arguments is not
> worth the extra burden of having labels, I'm not sure it is even
> desirable. I think that when arguments of a function are not always
> passed in the same order the readibility of the code is jeopardized.
That's where I think there are really two approaches and two
philosophies.
I thought that the goal of the merger between ocaml and olabl was not
only to integrate some olabl features, but also to merge the two
communities.
The O'Labl community has a different philosophy of programming.
If you consider this as an heresy, we cannot go very far.
I really believed that, especially because it is ocaml, we could have
an oecumenic approach: accept everybody and every style, with their
differences.
To achieve this goal, the classic mode was carefully designed so as to
allow what is your view of labels: an optional help to readability,
but no commutation.
It also ensures the interoperability between both styles.
I'm really curious of why you persist in ignoring its existence (and
the fact this is the default mode).
On the other hand, the modern mode, which should probably renamed
-labels or rather -commute mode, since this is its main difference
with classic mode, is really intended for people who more or less
agree with the olabl philosophy, and consider label commutation as an
important feature.
If you consider that such diversity is bad, and that there should be
only one mode, I do not really see what we can do. One solution would
be to have two different standard libraries, but this creates 3
problems:
* technical problems when linking code based on the two libraries.
* basically it doesn't solve the problem of all other libraries.
Non-labellers will still find that there are too many labels in
labelled libraries, and they will be discouraged from adding
documentation labels to their own libraries, which in turn will make
them incomfortable to use for labellers.
* ocamlbrowser looses a great part of its interest, which is to be
able to see the labels as documentation.
> > I suppose we should have a precise idea of what labels are there for.
> > For me they are a systematic mechanism, and the fact we allow to have
> > no label on one argument in function types is just a little comfort,
> > particularly useful when working with functionals.
>
> Wao! So, the normal evolution would be to have labels as ``a
> systematic mechanism'' ? I'm afraid this is really a dogmatic view,
> and may be some Caml users will object ...
The meaning of systematic here is just about the standard way to put
labels, not the way to use them. I do not intend to impose the use of
labels to anybody. Nor to force anybody to comply with the standard.
The theoretical background of the commuting mode is the selective
lambda-calculus, in which no label is also a label. From this point
of view having functions with no labels is just having functions where
all arguments have the same label. This is just a pain.
> > > # let sum l = List.fold_right ( + ) l 0;;
> > > val sum : int list -> int = <fun>
> >
> > Whether you consider this readable or not is a question of taste. I
> > personally believe that you need quite a background in HO formalisms
> > to understand the above. But of course most current Caml users have
> > this background.
>
> No, this is not a question of taste, since whatever readable you
> consider this definition, it is still more readable, easier to
> understand and easier to write than the labelized version:
I maintain this is a subjective judgment. The labelled version is in
my opinion easier to understand from an algorithmic point of view.
But I don't ask you to agree with that, just to admit that other
people may have other opinions. I perfectly respect yours.
> > Remark that here you didn't care about whether acc is the first or
> > second argument of the passed function, just because (+) is
> > commutative. However, in general functions are not commutative, and I
> > always have to think a lot when using List.fold_left or
> > List.fold_right without labels, just because I may write wrong code
> > without the type checker telling me anything.
>
> Yes functions in general are not commutative, but I cannot imagine how
> labels may prevent you from thinking when using List.fold_right...
Here is an example:
# let make_set l =
List.fold_right (fun x l -> if List.mem x l then l else x::l) [] l ;;
val make_set : 'a list -> 'a list = <fun>
At first sight it looks right. But if you look carefully you will
realise that in List.fold_right (contrary to List.fold_left) the
starting value comes last. So this function is just the identity!
Now, if I put a label (even in classic mode),
# let make_set l =
List.fold_right (fun x l -> if List.mem x l then l else x::l) acc:[] l ;;
^^
Expecting function has type 'a list -> acc:'b -> 'b
This argument cannot be applied with label acc:
The system tells me that this is wrong, and I do not have to think
carefully about whether the accumulator comes first or last.
I'm just waiting for the day we will discover this kind of bug inside
the compiler itself...
By the way, this also connects to the fact we need two different
typings for folding functions, depending on whether the used function
is a symmetrical one (which has no label) or an asymmetric one like
above.
That is, if considering both that having acc: in the expected function
is a pain, and that acc: on fold itself is useful, we choose the type
val fold_left : fun:('a -> 'b -> 'a) -> acc:'a -> 'b list -> 'a
we are no longer able to write fold_matrix as we can now:
# let fold_matrix fun:f = List.fold_left fun:(List.fold_left fun:f);;
val fold_matrix : fun:(acc:'a -> 'b -> 'a) -> acc:'a -> 'b list list -> 'a
So we need two functions: one with acc: in both places, and one with
neither.
> In several ways, I think labels may help obfuscating code, for
> instance if they allows you to treat the arguments of functions as a
> set instead of an ordered list: if arguments are never passed in the
> same order, the code is much harder to read, since it is much more
> difficult to remember the profile of functions (unless you remember
> the set of labels associated with the function's profile AND the types
> associated with the labels).
On what experience do you base this judgment ?
This is certainly not that of olabl users.
By the way the point is that you do not need to remember the profile
of functions at reading: it is explicit in the syntax. Labels are
often more informative than types.
> > > # let f ?style:x _ = x;;
> > > val f : ?style:'a -> 'b -> 'a option = <fun>
> > >
> > > As a pattern on the left-hand side x has type 'a, while as an
> > > expression on the right hand side it has type 'a option
> >
> > Well, internally the left-hand side is also 'a option, but option is
> > abbreviated because it is redundant. I do not think people want to see
> > the left-hand side option, but maybe this technical part should be
> > made more explicit in the manual.
>
> This is not the problem: the problem is this is an exception to a simple
> and intuitive rule of the Caml static semantics:
>
> ``In Caml, variables bound by patterns have a unique type''
>
> Corollary: when used in expressions, variables bound by patterns have
> the same type as their pattern type.
But this is the case (they have same type). I do not see how they
could have a different type. 'a is not the type of the pattern, but
of the expected value, and where option is abbreviated for the above
reason.
The "exception" actually is not the above case, but rather
# let f ?(:style = 0) () = style;;
val f : ?style:int -> unit -> int = <fun>
Here the type of the pattern is int (not int option, since there is a
default value), but the type of the expected value is int option.
We could make everything consistent and just say that the expected
value is of type int rather than int option, by requiring a default
value for all optional arguments, and hide completely the
implementation.
However, this would greatly weaken the system: the nice thing with
optional arguements is that they can depend on each other, and that we
can checked whether a value was passed or not. Remark also that if we
do that we loose nice properties of the current system (particularly
compared with other systems of default arguments, that are often based
on overloading), like eta-expansion:
# fun ?:style -> f ?:style;;
- : ?style:int -> unit -> int = <fun>
> > Conversion from 'a to 'a option is done at application. I do not
> > think there is any semantical problem here.
>
> I do think there is a problem here, since once more this violates a
> simple rule of Caml semantics:
>
> ``In Caml, there is no implicit coercions.''
No, this conversion is not based on types, and there is an untyped
semantics for optional arguments in both classic and commuting modes.
Types are used in the compiler, but just to make it more efficient.
> > > -- some expressions can be only written as arguments in an application
> > > context:
> > > # let f ?style:x g = ?style:x;;
> >
> > ?style:x is not an expression: labels are part of the application
> > node, not of the arguments.
>
> I know that and find it counter-intuitive, since once more it breaks
> a very simple and intuitive rule of Caml:
>
> ``Caml expressions possess the sub-term property: sub-expressions are
> also expressions''
This is false: (1 +) is not an expression in Caml.
> Once more, I know those technical reasons, but I do not get for
> granted that technical reasons are a valid answer to violations of the
> simplicity and intuitive understanding of the language. If technical
> reasons cannot handle some nice feature but force you to turn this
> feature into a bad hack, then you must choose not to incorporate the
> feature since it is no more a nice feature.
Yes in general, but do you really think that there is a deep
violation, and that this is a bad hack?
All the systems of optional arguments I know of are based on worse
hacks, like overloading, dynamic processing of a list of pairs
label/value, values in interfaces, etc...
I would rather say that we can be proud of avoiding all these.
> > Whether this is a good idea to have such syntactic sugar in
> > the language is an interesting question. However I believe it provides
> > some real comfort.
>
> You must carefully balance this comfort with the extra cost in terms
> of complexity and exceptions to the usual semantics (either static or
> dynamic) ...
Agreed. Dynamic semantics get (much) more complex. However, you can
understand it as a series of conservative extensions over the original
simple semantics of Caml.
> > Now, the problem we are talking about seems to boil down to higher
> > order functions in modern mode. And more particularly List.fold_left
> > and List.fold_right, since these are about the only two functions
> > where the argument itself has labels.
> >
> > It would cost nothing to add 4 more unlabelled functions to the List
> > module.
>
> And do the same for modules List and Hashtbl and Array and Set and
> Map, ... this will cost a bit I think.
Well, not in Hashtbl and Map since there the operation accepted by
fold takes 3 arguments, meaning that you won't apply it to an
arbitrary predefined operator.
So, this is only List, Array and Set (currently).
I don't know what is you scale for cost, but I just meant that this is
only adding
let foldl = fold_left
to the implementation, and
val foldl : ...
to the interface, with no code duplication.
By the way, this is not the first time we have to add one more
function just for typing reasons. Remember Sys.signal and
Sys.set_signal.
> > Or do you think that the problem is deeper, and that labels are
> > breaking the foundations of the language ?
>
> In my opinion some fundamental rules are broken in the current
> situation of the labels addition, and I think we should try to
> overcome this drawback to let the language simple to explain and
> use.
I'm afraid there is no miracle to expect there. OLabl itself was
based on serious theoretical work, and we have thought seriously
(together) of how to make OCaml 2.99 clean. If anybody has a perfect
solution, which also satisfies olabl users, then I applaude, but I am
very dubitative.
Amicalement,
Jacques
---------------------------------------------------------------------------
Jacques Garrigue Kyoto University garrigue at kurims.kyoto-u.ac.jp
<A HREF=http://wwwfun.kurims.kyoto-u.ac.jp/~garrigue/>JG</A>
This archive was generated by hypermail 2b29 : Tue Mar 21 2000 - 15:18:28 MET