Johann Spies <jspies@maties.sun.ac.za> writes:
> Thank you to everyone that replied. I have realized now that it is not
> that easy to start programming in ocaml (or a functional language)
> when you are an amateur and coming from a procedural/imperative
> programming background.
I wouldn't say that it is difficult, so much that there are some new
concepts to be learned.
> Maybe Sylvain summarized my problems with his questions:
>
>> There are many different things in your code. Answering to some
>> questions might help you:
>> + what is the type of lys?
>> + what is the type of the elements of lys?
>
> I am not sure about the difference between these two questions. I
> realised my mistake that lys was a char list and I expected wys_dit to
> print strings.
>From your session:
# let lys = ['a';'b';'c'];;
val lys : char list = ['a'; 'b'; 'c']
Based on this we can say that the type of lys is a char list, and the
type of the elements of lys is char. ('a', 'b', and 'c' are the
elements of lys).
>> + what is the type of wys_dit?
>
> As I understand it wys_dit is a function. I always thought of
> functions as using types not necessarily having types.
Even in C, you can think of functions as having a type. A function's
type encompasses its arguments and its retrn value. Let's look at
wys_dit:
# let wys_dit woord = print_string woord;;
val wys_dit : string -> unit = <fun>
According to the interpreter, the type of wys_dit is string -> unit.
This means that it is a function which takes as an argument a string
and returns a value of type unit. (BTW, in OCaml, the type unit is
sort of equivilent to C's void.)
>> + how many parameters do you want for wys_die_lys?
>
> I thought it needed one, originally, but it did not work, so I tried a
> second. At least the interpreter did not complain. Another response
> to my question, explained why I did not see an interpreter complaint.
>
>> + do you see the interpreter answers that it "expects" two
>> parameters?
>
> No, I did not see that. I do not always understand the messages of
> the interpreter.
Okay. Let's take a look:
# let rec wys_die_lys l = function
[] -> []
| h :: t -> wys_dit h :: wys_die_lys l t;;
val wys_die_lys : 'a -> string list -> unit list = <fun>
Okay, the interpreter says that the type of wys_die_lys is
'a -> string list -> unit list
The simplest explanation of this is that wys_die_lys is a function
that takes two arguments, one of which is of any type ('a), and the
second of which is a list of strings. It returns a list of units.
Why is this? Primarily because you are misusing the function
keyword. This is not surprising; function can be a confusing special
case to the beginner. More explained below:
>> + can you compare the function f1 and f2 such that:
>> let f1 x = match x with
>> | [] -> print_string "empty list";print_newline()
>> | h::t -> print_string "not empty list";print_newline();;
>> let f2 = function
>> | [] -> print_string "empty list";print_newline()
>> | h::t -> print_string "not empty list";print_newline();;
>> + how many parameters do they need?
>
> Maybe this is what I am struggling to understand. I can not see how
> f2 can work. What data does it use?
The thing is, function defines a function which takes one argument,
and does pattern matching on it. You don't see the argument above
because it is implicit. Until you understand this, use the first
pattern above which uses match.
> Related to this is what Benedikt referred to:
>
>> But the easiest way is:
>>
>> let wys_die_lys = List.iter print_string;;
>
> In this function, there is no explicit parameter. Where is this
> behaviour documented?
Okay, here is where I tell you that something I said above is a white
lie. A function with a signature of 'a -> string list -> unit list
does not really take two arguments. It actually takes one. The type
could be rewritten as:
'a -> (string list -> unit list)
I.e., it is a function which takes an argument of any type, and
returns a function that takes a string list and returns a unit list.
Whew! Guess what? You are learning about what is called "Currying".
Let's use a simple example to explain.
# let add (a, b) = a + b;;
val add : int * int -> int = <fun>
# add (1, 2);;
- : int = 3
Okay. Here add is a function with the type signature
int * int -> int
This means it takes a pair of ints and returns an int. In FP
parlance, int * int is a touple of two ints. int * string would be a
touple of an int and a string, and 'a * 'b * 'c would be a touple of 3
items of types 'a, 'b, and 'c. So, in practice, the function plus
above takes a single argument which just happens to be two ints, and
returns an int. Okay, lets look at a very similar function:
# let plus a b = a + b;;
val plus : int -> int -> int = <fun>
# plus 1 2;;
- : int = 3
Okay, here we have a slightly different function. It has the
following type signature:
int -> int -> int
As we can see, it seems to take two int arguments and returns an int.
But there is a little more going on here than that.
# let plusone = plus 1;;
val plusone : int -> int = <fun>
Okay, what is going on here? We are calling the plus function, but we
are only calling it with one argument! How does this work? Well, in
Caml, all functions really only take one argument. Lets look at the
type signature again, only let's add some parenthesies to make things
clearer:
int -> (int -> int)
plus is a function which takes an int, and returns a function which
takes an int and returns an int. Above, when we defined plusone, we
called plus with the int 1, and got back a function which takes and
int and returns an int. This is sometimes called partial function
application. Let's try it out.
# plusone 2;;
- : int = 3
Tadah! When you are calling making a function call like plus 1 2,
this is actually interpreted as ((plus 1) 2), i.e., call the function
returned by plus 1 with the argument 2.
Now, let's return to Sylvain's suggestion:
>> let wys_die_lys = List.iter print_string;;
# List.iter;;
- : f:('a -> unit) -> 'a list -> unit = <fun>
Okay, the type of List.iter is
('a -> unit) -> 'a list -> unit
[You can forget about the f:() stuff for now. These are labels, and
we can discuss those in another message.] Let's interpret this type.
List.iter takes as an arguemnt a function which takes an argument of
any type ('a), and returns a function which takes an argument of a
list of that type ('a list), and returns unit. Wow. That's a bit
complicated. Now let's look at the type of print_string.
# print_string;;
- : string -> unit = <fun>
That was simple enough. print_string takes an argument of type
string, and returns unit. Now, if you remember, List.iter takes as
it's argument a function from any type to unit. Let's use
print_string as the argument to List.iter.
# let wys_die_lys = List.iter print_string;;
val wys_die_lys : string list -> unit = <fun>
Now wys_die_lys is a function which takes a string list as an
argument, and returns a unit. this is exactly what you would have
gotten if you plugged in the signature from print_string into the
signature for List.iter above. As for what List.iter does, we will
have to refer to the manual. List.iter takes a function and a list of
items, and will apply that function to each of the items in order.
Hence, wys_die_lys will take a list of strings and call print_string
on each one.
Whew! My fingers are tired. I'll let you think about the above
before I try to reply to the rest of your post. I hope this has
helped a little.
--
Michael Welsh Duggan
(md5i@cs.cmu.edu)
-------------------
Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/
To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr