[ WWWFun | Software | O'Labl]

Introduction to O'Labl


Objective Label extends Objective Caml in 3 ways.

All these features are sound with respect to type checking, and integrate smoothly with other parts of O'Caml, like objects and classes.

Labeled arguments

Many languages, like Ada, Common Lisp, or SmallTalk already offer such a kind of feature. It is intended to enhance readability of programs.

However, except for Smalltalk, it seems that this possibility is not used very much in actual programming. Since in Ada and Common Lisp use of these labels in function calls is facultatory, programmers will most often ommit them.

Reflecting their success in the SmallTalk community, labels in O'Labl are SmallTalk style, with the possibility of providing different call patterns through optional parameters. Moreover they can be combined with Currying to allow out-of-order partial applications.

Why use labeled arguments in a functional programming language ?

  1. One reason is common with other languages: when a function takes many arguments, it becomes quickly difficult to remember their order and meaning. This is particularly true in functional programs, where one is often led to add parameters to function to avoid global variables.
    String.blit : string -> int -> string -> int -> int -> unit
    String.blit s1 0 s2 0 (String.length s1)
    
    With labels a function call is more than a sequence of symbols.
    String.blit : string -> pos:int ->
                  to:string -> to_pos:int -> len:int -> unit
    String.blit s1 pos:0 to:s2 to_pos:0 len:(String.length s1)
    
  2. Another reason, specific to strong typing, is that the label information is reflected by types. The concept of type-as-documentation is often used in functional programming, and labels make it stronger, as you can see in the example above.
    A common practice in absence of labels is to define type aliases and use their names as documentation, but this does not allow for relative information, that is, what is the role of this argument in this function.

  3. A third reason, specific to functional programming, is the use of functionals. Generally the argument function is taken first, which means that all other arguments have to be written after it. If you write the function inline, this leads to unreadable code.
    List.fold_left
      (fun st x ->
            ....
            ....)
      a l
    
    With labels, you can freely change the order.
    List.fold_left l st:a
      fun:(fun x st:st ->
            ....
            ....)
    
  4. Last, and again specific to functional programming, this allows for a greater variety of partial applications. You are not retricted anymore to applying in the definition order. For instance:
    # String.blit to:s2 to_pos:0;;
    - : string -> pos:int -> len:int -> unit = <fun>
    

Optional parameters

Labeled arguments are a nice feature, and particularly useful with functional programming, but their success in SmallTalk comes from the use of call patterns. That is, depending on the labels you use in your message, different methods will be called.

In O'Labl this is also possible with functions, within the limits of the type system. That is, the same function may have different call patterns, but they must all

  1. return values of the same type.
  2. take values of the same types on identical labels.
  3. and all combinations of presence/absence must be allowed

For instance, we can define a powerful string_copy function, based on the above String.blit function.

# let string_copy s1 to:s2 ?:pos [< 0 >] ?:to_pos [< 0 >]
      ?:len [< min (String.length s1 - pos) (String.length s2 - to_pos) >]
    = String.blit s1 :pos to:s2 :to_pos :len ;;
val string_copy : string -> to:string ->
                  ?pos:int -> ?to_pos:int -> ?len:int -> unit
In fact the above syntax in an abbreviated form for the following code, where ? denotes optional parameters.
# let string_copy s1 to:s2 ?pos:opos1 ?to_pos:opos2 ?len:olen =
    let pos1 = match opos1 with None -> 0 | Some x -> x in
    let pos2 = match opos2 with None -> 0 | Some x -> x in
    let len = match olen with Some -> x
              | None -> min (String.length s1 - pos1) (String.length s2 - pos2)
    in String.blit s1 pos:pos1 to:s2 to_pos:pos2 len:len ;;
As you can see, the dispatch is made dynamically. This means that, by using this last form of definition, one can program almost any pattern selection. On the other hand, the abbreviated form used above is powerful enough for most cases.

Calling a function with optional parameters is done exactly the same way as without optionals.

# let s1 = "12345" and s2 = "12345678";;
val s1 : string = "12345"
val s2 : string = "12345678"
# string_copy s1 to:s2;;
> String.blit s1 to:s2 pos:0 to_pos:0 len:5
- : unit = ()
# string_copy s1 to:s2 len:3;;
> String.blit s1 to:s2 pos:0 to_pos:0 len:3
- : unit = ()
# string_copy s1 to:s2 pos:2;;
> String.blit s1 to:s2 pos:2 to_pos:0 len:3
- : unit = ()
Partial application is still possible.
# string_copy to:s2 to_pos:2;;
- : string -> ?pos:int -> ?len:int -> unit = <fun>

Polymorphic variants

Another new feature, independent from the two previous, is the availability of polymorphic variants. They are simply sum types that you don't need to define (their types are inferred).

You will mainly use them in two cases:

The first case happens when objects take their values in different ranges.

type number = [int(int) float(float)]
and basic = [int(int) float(float) bool(bool) char(char) string(string)]
One can view number as a subtype of basic. Now if I define print_basic properly, it will also work with values of type number.
# let print_basic = function
    `int i -> print_int i
  | `float x -> print_float x
  | `bool b -> print_bool b
  | `char c -> print_char c
  | `string s -> print_string s ;;
val print_basic : [<int(int) float(float) bool(bool) char(char) string(string)] -> unit
# print_basic (`char 'a');;
a- : unit
# print_basic (`int 1 : number);;
1- : unit
The second case is again useful to make programs more readable and type safe. For instance I can define a function with type:
val search_string : string -> in:string -> mode:[exact subcase regexp] -> index
It is of course possible to do it by defining explicitly a search_mode type, but then you always have to refer explicitly to the module where it was defined, which is a pain when the information is of communication nature.

Polymorphic methods

This is the most recent addidition to Objective Label. Please refer to the DVI and postscript version of the manual for how to use them.

For more detailed information, see the manual.


Jacques Garrigue, 96.10.1.