If 문, 루프, 재귀

if 문 (진짜 if문)

Ocaml 은 두가지 다른 형태의 명백한 if문을 취한다 :

if boolean-condition then expression
if boolean-condition then expression else other-expression

여태까지 사용해왔던 다른 언어들과는 다르게, ocaml에서 if문은 진짜 표현식이다. Ocaml의 if문은 사실 다음과 더 비슷하다. boolean-condition ? expression :other-expression

다음은 if문의 간단한 예제이다:

let max a b =
  if a > b then a else b
  ;;

보면 알겠지만, 위의 max 함수의 타입을 toplevel에서 보면, Ocaml 은 이 함수가 Polymorphic 하다는것 아래처럼 알 수있다:

max : 'a -> 'a -> 'a

따라서 Ocaml에서 max 함수는 아무 type 으로도 사용할 수 있다:

# max 3 5;;
- : int = 5
# max 3.5 13.0;;
- : float = 13.
# max "a" "b";;
- : string = "b"

이 이유는 > 가 원래부터 polymorphic 하기 때문에 어떠한 type 이든 심지어 object 까지도 연산할 수 있다.(사실 비트를 가지고 비교하기 때문).

[Note maxmin는 라이브러리에 포함되어 있다]

이제 if 문에 대해서 좀더 자세히 알아보자. range 는 충분히 설명하지 않은 함수인데, 아마도 여태까지 배웠던 내용을 동원해서 이 재귀 함수와 리스트 그리고 if문이 무엇을 하는지 살펴보자:

let rec range a b =
  if a > b then []
  else a :: range (a+1) b
  ;;

이제 이 함수에 몇가지 예제를 적용해 보자. 먼저 쉬운 예로 a > b 부터 해보도록 하자. range 11 10 하면 [] 가 되돌려 진다.(빈 리스트) 그리곤 끝이다.

range 10 10를 했을때는 어떨까? 10 > 10가 거짓이 되니까 else부분이 계산되어진다, 그렇다면: 10 :: (range 11 10) (가독성을 위해 괄호를 넣었다). 방금전에 했던 것처럼 range 11 10 = []다, 따라서 이것은: 10 :: []이다. 앞에서 설명했던 리스트의 :: (cons) 연산자가 기억나는가? 10 :: [][ 10 ] 같다.

이것을 한번 해보자 range 9 10:

   range 9 10
=> 9 :: (range 10 10)
=> 9 :: [ 10 ]
=> [9; 10]

range 1 10 은 명확하게 다음과 같이 계산되어진다. [ 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 ].

What we've got here is a simple case of recursion. Functional programming can be said to prefer recursion over loops, but I'm jumping ahead of myself. We'll discuss recursion more at the end of this chapter.

Back, temporarily, to if statements. What does this function do?

let f x y =
  x + if y > 0 then y else 0
  ;;

Clue: add brackets around the whole of the if expression. It clips y like an electronic diode.

The abs (absolute value) function is defined in Pervasives as:

let abs x =
  if x >= 0 then x else -x

Also in Pervasives, the string_of_float function contains a complex pair of nested if expressions:

let string_of_float f =
  let s = format_float "%.12g" f in
  let l = string_length s in
  let rec loop i =
    if i >= l then s ^ "."
    else if s.[i] = '.' || s.[i] = 'e' then s
    else loop (i+1)
  in
  loop 0
  ;;

Let's examine this function. Suppose the function is called with f = 12.34. Then s = "12.34", and l = 5. We call loop the first time with i = 0.

i is not greater than or equal to l, and s.[i] (the ith character in s) is not a period or 'e'. So loop (i+1) is called, ie. loop 1.

We go through the same dance for i = 1, and end up calling loop 2.

For i = 2, however, s.[i] is a period (refer to the original string, s = "12.34"). So this immediately returns s, and the function string_of_float returns "12.34".

What is loop doing? In fact it's checking whether the string returned from format_float contains a period (or 'e'). Suppose that we called string_of_float with 12.0. format_float would return the string "12", but string_of_float must return "12." or "12.0" (because floating point constants in OCaml must contain a period to differentiate them from integer constants). Hence the check.

The strange use of recursion in this function is almost certainly for efficiency. OCaml supports for loops, so why didn't the authors use for loops? We'll see in the next section that OCaml's for loops are limited in a way which prevents them from being used in string_of_float. Here, however, is a more straightforward, but approximately twice as slow, way of writing string_of_float:

let string_of_float f =
  let s = format_float "%.12g" f in
  if String.contains s '.' || String.contains s 'e'
  then s
  else s ^ "."
  ;;