Re: Syntax for label, NEW PROPOSAL

From: Jacques Garrigue (garrigue@kurims.kyoto-u.ac.jp)
Date: Sat Mar 18 2000 - 12:59:40 MET

  • Next message: Jacques Garrigue: "Re: Syntax for label, NEW PROPOSAL"

    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