Version française
Home     About     Download     Resources     Contact us    
Browse thread
[Caml-list] Easy solution in OCaml?
[ Home ] [ Index: by date | by threads ]
[ Search: ]

[ Message by date: previous | next ] [ Message in thread: previous | next ] [ Thread: previous | next ]
Date: -- (:)
From: Brian Hurt <brian.hurt@q...>
Subject: Re: [Caml-list] Easy solution in OCaml?
On Sat, 26 Apr 2003, Siegfried Gonzi wrote:

> >I find that if you're working against the compiler, either a) you haven't 
> >thought the problem through, b) there's an easy solution you're missing, 
> >or c) you have a bug.
> >
> I think this is a very interesting question. What I love about Scheme 
> (or even Python lists) is the fact that you can put into a list what you 
> want. I had a problem the other day which was as follows:
> I have a Scheme function which extracts floating-point values from 
> strings located in a file:
> ==
> 12.33,43.4,4.56,nan,1.23
> 23.3,34.4,nan,1.2,0
> ...
> ==
> The extracted floating point numbers become stored into an array:
> (vector (vector 12.33 43.4 4.56 -1.0 1.23) (vector 23.3 34.4 -1.0 1.2 0.0))
> My boss gave me a file which he urgently had to evaluate in order to 
> fullfill a time schedule (some measurements from  the field of 
> experimental physics). But there was a problem, because the file was as 
> follows:
> ==
> name1,2.23,2.23,23.4
> name2,23.34,23.34,.223
> ...
> ==
> The first entry was an annotation but my Scheme function expects a 
> string which can be converted to a floating point number. But Scheme is 
> your friend here, because one more line in my file-reading function and 
> you get something like this:
> ((name1 2.23 2.334) (name2 3.34 23.2 ...))
> In Ocaml I would have to skip the first entry because it is not a 
> floating-point value. All my other functions were not affected, because 
> passing around arrays or lists does not mean you must put forward 
> floating-points or string arrays or whatever.

You have to skip the first entry anyways, because it's not a floating
point value.  Or you have to have special handling for strings and floats
seperately- every time you pull an element out of the list, you need to go
"is this a string or a float?" and handle it correctly.  Otherwise, please
define what "name1" /. 3.0 should be.

How you put strings and floats into the same list is you define a type, 
call it foo_t for the moment:

type foo_t =
    String of string
    | Float of float

And make it a list of foo_t's.  You can now pick apart your list by, for

let print_list lst =
    let rec loop lst =
        match lst with
            [] -> () (* End the recursion *)
            | String(s) :: t ->
                print_string (s ^ " ");
                loop t
            | Float(x) :: t ->
                print_string ((string_of_float x) ^ " ");
                loop t
    print_string "( ";
    loop lst;
    print_string ")\n"

Now, let's say at a later point you want integers in your string as well, 
so you extend foo_t to be:

type foo_t =
    String of string
    | Float of float
    | Int of int

But being a forgetfull sort (like I am all the time), you forget to update 
print_string.  The next time you try to compile, ocaml tells you:

File "", line 10, characters 8-274:
Warning: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
Int _::_

You constantly see (or at least I constantly see) the same pattern 
repeated over and over- pulling an Object out of a data structure and then 
running it through a tree of if typeofs to figure out what sort of 
creature you just pulled out.  And when you add a new type to the 
structure, all those if trees need a new branch.  In Ocaml, you can do the 
same thing, but the compiler checks your work and warns you if you forgot 
a place.

If the string is always just the first element, you might consider using a
tuple or structure.  But the short form is, you have a problem.  And one
the language shouldn't just let you sweep under the rug, as we'll see.

> I agree upon that the above feature can sometimes lead to bad hacks, 
> because you the return value of a function can consist of a list where 
> you put things into the list which you decide later on whether you want 
> augment the list by other parameters, for example:
> Your first version of the function has as return value: (list (list 2 3 4))
> A year later you decide you want something like this: (list (list 2 3 4) 
> (vector 3 4 5) "hi guy")
> The goody here is all your other functions which expect the output of 
> the above function must not be re-written, as opposed to Clean or OCaml 
> where you would have to re-write all your functions because the 
> structure of your return-list has changed.

If you don't know what you just pulled out of a list, how can you do 
anything with it?  About all you can do with it is to move it around.  
Take it out of one data structure and put it into another.  Or pass it to 
some other function.  Which you can quite happily do with Ocaml- take a 
look at or Array.of_list in the standard libraries for examples.

But once you want to do something more with the elements than just move 
them around, you need to know what they are- what type they are.  So let's 
say I'm expecting that I'm dealing with an int list list (your first 
example there).  I could easily convert this to a float list list by 

let conv lst = (fun l -> (float_of_int) l) lst

With some suitable generic handling, instead of calling, I just 
call map, so when I hit an array of ints instead of a list of ints, I call and not  It could be done, it'd be of limited use but 
it could be done.  But what am I supposed to do with the string "hi guy"?  
Should it treat the string as a list of char, implicitly convert it to a 
list of int, and then convert the list of int to a list of float, 
returning [104.; 105.; 32.; 103.; 117.; 121.]?  What sensible thing can 
you do here?

This is what I meant about a bug in your program or not thinking the 
design through.  If there is a reason you're putting a string into that 
list, it means something.  Decide what it means, and at each point you 
handle elements from that list, decide what to do if you see a string- 
even if it's just "ignore it and go on", think about it and decide.

> >This is usefull for two reasons, in my experience: sometimes, it lets the 
> >compiler produce better code (for example, consider the function:
> >let foo x y = x == y
> >If you know that x and y will always be integers, doing:
> >let foo (x:int) (y:int) = x == y
> >allows the compiler to inline integer equals, rather than calling the 
> >generic compare function- much faster).
> >
> I doubt that type correctness is always better or leads to failure-free 
> program execution.  

Type correctness is not a panacea- bugs can still sneak past.  Not even 
proofs of code correctness is good enough- they just prove the code does 
what it's designed to do, not what it's *supposed* to do.  Unfortunately, 
no CPU architecture I know of includes a DWIM instruction (despite long 
standing and unremitting demand)...

Type correctness does catch a lot of bugs, often even surprisingly deep 
bugs.  And every bug the compiler catches is one less you have to hunt 
down yourself...

> Yesterday I faced the following situation: dividing 
> the two vectors:
> (vector 0.0 0.0  23.34 23.4)
> through
> (vector 0.0 0.0 0.0 23.4)
> I forgot to check that division by zero is not a good idea, but the good 
> old Bigloo compiler didn't rebel and had as output:
> (vector #f #f #f 1.0)
> Maybe a bad example because in OCaml you could use exceptions or 
> something like this, but in this case the OCaml program had aborted 
> (this is also true for C,...). I am not sure how long it would have gone 
> good, but the Scheme program had not aborted in a safety-critical 
> system-environment. I am really often surprised how forgiving Scheme and 
> also CommonLisp actually are in such situations.

Hmm?  What platform are you on?  Linux on x86:

$ ocaml
        Objective Caml version 3.06

# 1. /. 0. ;;
- : float = inf.
# 0. /. 0. ;;
- : float = nan.
# 0. /. 1. ;;
- : float = 0.
# let x = [ 0.0 ; 1.0 ; 0.0 ; 1.0 ]
  and y = [ 0.0 ; 0.0 ; 1.0 ; 1.0 ] ;;
val x : float list = [0.; 1.; 0.; 1.]
val y : float list = [0.; 0.; 1.; 1.]
# let rec vdiv a b =
  match a, b with
      [], [] -> []
      | x :: at, y :: bt -> (x /. y) :: (vdiv at bt)
      | _ -> assert false
val vdiv : float list -> float list -> float list = <fun>
# vdiv x y ;;
- : float list = [nan.; inf.; 0.; 1.]

No exceptions for me.


To unsubscribe, mail Archives:
Bug reports: FAQ:
Beginner's list: