Version française
Home     About     Download     Resources     Contact us    
Browse thread
Re: Syntax for label
[ Home ] [ Index: by date | by threads ]
[ Search: ]

[ Message by date: previous | next ] [ Message in thread: previous | next ] [ Thread: previous | next ]
Date: -- (:)
From: Jacques Garrigue <garrigue@k...>
Subject: Re: Syntax for label, NEW PROPOSAL
From: Pierre Weis <Pierre.Weis@inria.fr>

> > *** Proposal
> > 
> > Objective Caml 3.00 is not yet released, and I believe we can still
> > have modifications on this point.
> 
> Yes, you're perfectly right, we can still modify several points.
> However, I think there are many other points that are more important
> than the choice of ``%'' instead of ``:'', which is only cosmetic
> after all.

Well, I think I've got enough reactions to rule out %.
These are of the small details of syntax you want to check for
sure. And that are very hard to change afterwards.

> Thus, I would prefer to discuss deeper and more semantic problems:
> 
> -- Problem1: labels can be reserved keywords. This is questionable
> and it has been strongly criticised by some Caml users, especially when
> reading in the code the awful sequence fun:begin fun ...

Well, maybe just because I've grown used to it, I do not find it awful
at all.

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.

On the other hand, their particular use in the standard library may be
another subject of discussion.
If I remember correctly, there are only 2 keywords used as label in
the standard library: `fun' (in functionals) and `to' (in output
functions).

If we want to remove them, I think a good policy would be to change
the "default" length of labels from 3 to 4. Since many Caml keywords
are of length 3, we avoid lots of conflicts!

This would mean:

fun -> func
to -> chan or dest
acc -> accu             :-)
src -> orig             (src meant a lot to a now disappeared company)
dst -> dest             (maybe because I'm French, dst reminds me of something)
buf -> buff             ???
...
Still it seems reasonable to keep pos: and len:.

Beware however that label changes are inherently dangerous: you often
realize afterwards that a choice was not so good, and I really hoped
that we could definitively fix the standard library labels in 3.00.

> -- 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 would cite:
> 
>    * the labels completely redundant with the types
>      (E.g. char:char in the type of String.contains or String.index)

Following the above policy, I would say that these are not ideal
choices, but then what other possibility is there?

Moreover, even if this label is redundant in types, it is not
necessarilly so in code.

>    * undesired labels: in many cases I don't want to have labels just
>      because I don't want to remember their names. (E.g., I very often
>      mispell the label acc since I've always used accu to name an
>      accumulator; furthermore, when I do not mispell this label, I feel
>      acc:accu extremely verbose). Also because labels are verbose at
>      application.

Isn't there a classic mode for that?
If you think that labels are verbose, you can use it, and it even
provides some partial support for label checking when you want it.

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.

>    * labels that prevent you to use comfortably your traditional functions.
>      This is particularly evident for the List.map or List.fold_right
>      higher-order functionals.
> 
> This last point is a real problem. Compare the usual way of using
> functionals to define the sum of the elements of a list:
> 
> # 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.

> # let sum l = List.fold_right ( + ) l 0;;
>                               ^^^^^
> This expression has type int -> int -> int but is here used with type 'a list

Aargh, that's right, error messages are a pain. I believe I worked a
lot making them informative, but there are still difficult situations.
I'll try to improve this.

Remark however that, in modern mode, it is already strange to have an
application with no labels at all...

> The real problem is that fixing the code makes no good at all to its
> readability (at least that's what I would say):
> 
> # let sum = List.fold_right fun:begin fun x acc:y -> x + y end acc:0;;
> val sum : 'a -> int list -> int = <fun>

You do not have to use begin .. end everywhere.
I do personally write:
# let sum = List.fold_right acc:0 fun:(fun x :acc -> x + acc);;

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.

> For all these reasons, I would suggest to carefully use labels into
> the standard libraries: 
> 
> -- remove labels from higher-order functional
> -- remove redundant labels: when no ambiguity can occur you need not
>    to add a label.
> -- use labels when typechecking ambiguity is evident (for instance
> when there are two or more parameters with the same type).
> 
> Labels must enforce readability of code or help documenting the
> libraries, it should not be an extra burden to the programmer and a
> way of offuscating code.

This seems to boil down to using labels for documentation purposes
only. If you reduce them to such an extent, I'm afraid they will not
do very much for code readability anymore. And I stated above, you get
typechecking holes in functionals.

By the way, I do not see how labels can help obfuscating code.
What you are probably intending to say is that they make more
difficult to write some combinator-based HO-style code. Does it mean
we should have a version of the List module without labels for such
uses?

> Evidently, as any other extension, labels must not offuscate the
> overall picture, that is they must not clobber the semantics, nor add
> extra exceptional cases to the few general rules we have for the
> syntax and semantics of Caml.
> 
> In this respect, optional labelled arguments might also be discussed,
> particularly for the following facts:
> 
> -- syntactically identical patterns and expressions now may have
> incompatible 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.

Conversion from 'a to 'a option is done at application. I do not
think there is any semantical problem here.

> -- 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.

> -- the simple addition of a default value to an optional argument may
>    trigger a typechecking error:
> 
>    # let f ?(style:x) g = g ?style:x;;
>    val f : ?style:'a -> (?style:'a -> 'b) -> 'b = <fun>
> 
>    # let f ?(style:x = 1) g = g ?style:x;;
>    This expression has type int but is here used with type 'a option

That one is more serious. This is not a "simple addition". Default
values unroll some syntactic sugar, changing the type of the
argument. 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.

> Do not forget the design decision that has always been used before in
> the development of Caml: interesting but not universal extensions to
> the language must carefully be kept orthogonal to the core language
> and its libraries. This has been successfully achieved for the
> important addition of modules (that do not prevent the users from
> using the old interface-implementation view of modules) as well as for
> the objects system addition that has been also maintained orthogonal
> to the rest of the language (in particular the standard library has
> never been ``objectified''). I don't know of any reason why labels
> cannot follow the same safe guidelines.

I just believed that it was agreed that labelizing the standard
library was a progress. Of course this also means that you do not stay
100% compatible in modern mode. Still, there are no optional arguments
in the standard library, meaning that you stay 100% compatible in classic
mode.

> There are also people around that would like to keep Caml a true
> functional language, where usage of higer order functions is easy and
> natural.  We have to be careful not to lose what is the actual
> strength of the language.

Of course, I agree with that.

A first approach would be to say that one can always use classic mode.
Modern is not pedantic, it is just another typing discipline.
But this would endanger the unicity of the language, so this cannot be
an universal answer.

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.

val foldl : fun:('a -> 'b -> 'a) -> 'a -> 'b list -> 'a
val foldr : fun:('a -> 'b -> 'b) -> 'a list -> 'b -> 'b
val foldl2 : fun:('a -> 'b -> 'c -> 'a) -> 'a -> 'b list -> 'c list -> 'a
val foldr2 : fun:('a -> 'b -> 'c -> 'c) -> 'a list -> 'b list -> 'c -> 'c

This way you can write

# let sum l = List.foldr fun:(+) l 0

The same additions would also apply to the Array module.
There is also the problem of iteri and mapi, where i: may be not
that important in practice (even when working on an array of integers,
I wouldn't expect anybody to make such a mistake).

val iteri : fun:(i:int -> 'a -> unit) -> 'a array -> unit
val mapi : fun:(i:int -> 'a -> 'b) -> 'a array -> 'b array

One may even insist that fun: is superfluous.
Then it would probably be better to have another List module without
labels at all. Again, this is pretty easy to do, and does not incur
any code blow in general.

Or do you think that the problem is deeper, and that labels are
breaking the foundations of the language ?

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>