Ficha OCaml 0: Os primeiros passos com OCamlEx. Como compilar?Ex. A diferença entre ocamlopt e ocamlc.Ex. Bestiário de Erros de Compilação.Ex. Compilação de programas que usam bibliotecas externasEx. Perfilar um programa.Ex. Geração de Documentação.Ex. Uma ferramenta para o advogado do diabo.
Esta ficha tem por objetivo a vossa familiarização com as ferramentas básicas de compilação e execução de programas OCaml. Não importa assim tanto aqui perceber em detalhe os programas OCaml que vamos utilizar. Estes vos parecerão familiar mais adiante nesta Unidade Curricular. Não nos debruçaremos sobre o uso dos compiladores que produzem javascript.
Considere a seguinte implementação do algoritmo designado de Trabb-Pardo-Knuth. Este algoritmo não tem nenhuma outra utilidade fora o de ser um algoritmo da classe "hello world!" que exemplifica muito comodamente e simplesmente algumas características da linguagem de programação usada.
1ler 11 números para uma sequência A
2para cada item na sequência A, do último ao primeiro
3 calular uma função sobre este item
4 se o resultado ultrapassar um limite predefinido
5 alertar o utilizador
6 senão
7 imprimir o resultado a função
Uma tradução literal deste algoritmo em Ocaml é:
x1open Float
2open Array
3
4let f t = sqrt (abs t) +. 5. *. (t ** 3.)
5
6let a = init 11 (fun _ -> read_float ())
7
8let () =
9 for i=10 downto 0 do
10 let msg =
11 let y = f a.(i) in
12 if y <= 400. then (string_of_float y) else "TOO LARGE" in
13 print_endline msg
14 done
15
Neste exemplo definimos a função por usar como sendo
Passe este programa para um ficheiro de nome tpk0.ml
.
Compile este ficheiro com o compilador ocamlopt
da seguinte forma: ocamlopt tpk0.ml -o tpk0n
.
Compile este ficheiro com o compilador ocamlc
da seguinte forma: ocamlc tpk0.ml -o tpk0b
.
Execute os dois programas (tpk0n
e tpk0b
) na linha de comando com base em 11 valores introduzidos no teclado. Por exemplo
xxxxxxxxxx
241$ ./tpk0n
2-1
354
42
573
64
7100
8466
9400
1010000
11-76
1239
13TOO LARGE
14-2194871.2822
15TOO LARGE
16TOO LARGE
17TOO LARGE
18TOO LARGE
19322.
20TOO LARGE
2141.4142135624
22TOO LARGE
23-4.
24
ou, se colocamos as entradas num ficheiro input.txt
(desejável) :
xxxxxxxxxx
131$ ./tpk0n < input.txt
2TOO LARGE
3-2194871.2822
4TOO LARGE
5TOO LARGE
6TOO LARGE
7TOO LARGE
8322.
9TOO LARGE
1041.4142135624
11TOO LARGE
12-4.
13
Experimente o programa dentro de um toplevel. Aconselhamos o uso de utop
. Nota: depois do copy-paste do código, não se esqueça de juntar dois pontos-e-vírgulas ;;
e carregar no enter
.
Uma versão mais no espírito da programação funcional é :
xxxxxxxxxx
71open List
2let v = (fun _ -> read_float ()) |> (init 11) |> rev
3let f x =
4 let t = (Float.sqrt (Float.abs x) +. 5. *. (x ** 3.)) in
5 t |> (fun u -> if u > 400. then "TOO LARGE" else string_of_float u)
6 |> print_endline
7let () = iter f v
Compile com os dois compiladores e execute as duas versões.
Considere o seguinte código OCaml
:
xxxxxxxxxx
111let () =
2 for i=1 to int_of_string Sys.argv.(1) do
3 print_float (Random.float (float_of_string Sys.argv.(2))); print_newline ()
4 done
5(* (* uma forma alternativa *)
6let () =
7 for i=1 to int_of_string Sys.argv.(1) do
8 Sys.argv.(2) |> float_of_string |> Random.float |> print_float ;
9 print_newline ()
10 done
11*)
gen_floats.ml
e compile com ocamlopt gen_floats.ml -o gen_floats
../gen_floats X Y
gere X
números flutuantes entre 0 e Y
(excluído), na saída standard. Experimente a sua execução, por exemplo com os valores 11
e 800
: ./gen_floats 11 800
.xxxxxxxxxx
311(* os parâmetros fornecidos na linha de comando *)
2let amostras = int_of_string Sys.argv.(1)
3let quantos = int_of_string Sys.argv.(2)
4let limite = float_of_string Sys.argv.(3)
5
6(* a função por avaliar*)
7let f t = Float.sqrt (Float.abs t) +. 5. *. (t ** 3.)
8
9(* o algoritmo tpk *)
10let tpk () =
11 let a = Array.init quantos (fun _ -> Random.float limite) in
12 for i= quantos - 1 downto 0 do
13 let msg =
14 let y = f a.(i) in
15 if y <= 400. then (string_of_float y) else "TOO LARGE" in
16 print_endline msg
17 done
18
19(* para medir o tempo entre o início e o fim de uma execução de f com o argumento x*)
20let time f x =
21 let t = Sys.time() in
22 let fx = f x in
23 let () = Printf.eprintf "Execution time: %fs\n" (Sys.time() -. t) in
24 fx
25
26(* avaliar "amostras" vezes a função f sobre x *)
27let eval f x = for i=1 to amostras do f x done
28
29(* execução principal *)
30let () = time (eval tpk) ()
31
Vamos executar o código do exercício anterior muitas vezes sobre dados gerados aleatoriamente e medir o tempo. Iremos assim mostrar na prática uma diferença de desempenho entre a versão compilada por ocamlc
e ocamlopt
.
compile este programa com os dois compiladores, para tpkbenchb
na versão bytecode e tpkbenchbn
na versão nativa.
Execute cada um dos executáveis por forma a que a saída standard seja ignorada, que haja um milhão de execução do algoritmo de trabb-pardo-knuth
.
Deverá obter algo semelhante aos tempos seguintes :
xxxxxxxxxx
41$ ./tpkbenchb 1000000 11 800 > /dev/null
2Execution time: 9.682373s
3$ ./tpkbenchn 1000000 11 800 > /dev/null
4Execution time: 6.205420s
Voltemos ao exemplo original. Explique para cada uma das alíneas seguintes o(s) erro(s) que encontra.
Alguns deles são detetados na compilação outros na execução. Proponha uma correção.
xxxxxxxxxx
141open Float
2open Array
3
4let f t = sqrt (abs t) + 5. *. (t ** 3.)
5
6let a = init 11 (fun _ -> read_float ())
7
8let () =
9 for i=10 downto 0 do
10 let msg =
11 let y = f a.(i) in
12 if y <= 400 then (string_of_float y) else "TOO LARGE" in
13 print_endline msg
14 done
xxxxxxxxxx
121let f t = sqrt (abs t) +. 5. *. (t ** 3.)
2
3let a = init 11 (fun _ -> read_float ())
4
5let () =
6 for i=10 downto 0 do
7 let msg =
8 let y = f a.(i) in
9 if y <= 400. then (string_of_float y) else "TOO LARGE" in
10 print_endline msg
11 done
12
xxxxxxxxxx
151open Float
2open List
3
4let f t = sqrt (abs t) +. 5. *. (t ** 3.)
5
6let a = init 11 (fun _ -> read_float ())
7
8let () =
9 for i=10 downto 0 do
10 let msg =
11 let y = f a.(i) in
12 if y <= 400. then y else "TOO LARGE" in
13 print_endline msg
14 done
15
xxxxxxxxxx
121open Float
2
3let f t = sqrt (abs t) +. 5. *. (t ** 3.)
4
5let a = Array.init 11 (fun _ -> read_line ())
6
7let () =
8 for i=10 downto 0 do
9 let msg =
10 let y = f a.(i) in
11 if y <= 400. then (string_of_float y) else "TOO LARGE" in
12 print_endline
xxxxxxxxxx
131open Float
2
3let f t = sqrt (abs t) +. 5. /. (10. -. t)
4
5let a = Array.init 11 (fun _ -> read_float ())
6
7let () =
8 for i=11 downto 0 do
9 let msg =
10 let y = f a.(i) in
11 if y <= 400. then (string_of_float y) else "TOO LARGE" in
12 print_endline msg
13 done
xxxxxxxxxx
131open Float
2
3 let f t = sqrt (abs t) +. 5. *. (t ** 3.)
4
5 let b = Array.init 11 (fun _ -> read_float ())
6
7 let () =
8 for i=10 downto 0 do
9 let msg =
10 let y = f 0.5 a.(i) in
11 if y <= 400. then (string_of_float y) else "TOO LARGE" in
12 print_endline msg
13 done
xxxxxxxxxx
131open Float
2
3let f t = sqrt (abs t) +. 5. *. (t ** 3.)
4
5let a = Array.init (fun _ -> read_float ())
6
7let () =
8 for i=10 downto 0 do
9 let msg =
10 let y = f a.(i) in
11 if y <= 400. then "TOO LARGE" in
12 print_endline msg
13 done
Quer saber mais ? Veja Common Error Messages.
O objetivo deste exercício é perceber como compilar programas que fazem uso de bibliotecas externas, isto é, de funcionalidades que não pertençam a biblioteca padrão. A página Compiling OCaml projects lista as diferentes formas de compilar um programa conforme as bibliotecas que pode vir a usar. Vamos aqui ilustrar este processo com um programa que usa a biblioteca gráfica Graphics.
Esta instala-se de forma habitual:
xxxxxxxxxx
11opam install graphics
Recupere o ficheiro graph_example.ml
no seguinte site (link).
ocamlfind
conforme a documentação indicada no início do exercício.
O objetivo deste exercício é conhecer como se obtém um perfil de um determinado programa. Vamos fazê-lo para o programa executado em modo bytecode, mas de forma muito semelhante se faz o perfil de um programa em modo nativo. Para mais opções sobre como perfilar um programa, ver a documentação do ocamlprof.
Consideremos novamente o programa de teste temporal do algoritmo TPK.
xxxxxxxxxx
311(** os parâmetros fornecidos na linha de comando *)
2let amostras = int_of_string Sys.argv.(1)
3let quantos = int_of_string Sys.argv.(2)
4let limite = float_of_string Sys.argv.(3)
5
6(** a função por avaliar*)
7let f t = Float.sqrt (Float.abs t) +. 5. *. (t ** 3.)
8
9(** o algoritmo tpk *)
10let tpk () =
11 let a = Array.init quantos (fun _ -> Random.float limite) in
12 for i= quantos - 1 downto 0 do
13 let msg =
14 let y = f a.(i) in
15 if y <= 400. then (string_of_float y) else "TOO LARGE" in
16 print_endline msg
17 done
18
19(** para medir o tempo entre o início e o fim de uma execução de f com o argumento x*)
20let time f x =
21 let t = Sys.time() in
22 let fx = f x in
23 let () = Printf.eprintf "Execution time: %fs\n" (Sys.time() -. t) in
24 fx
25
26(** avaliar "amostras" vezes a função f sobre x *)
27let eval f x = for i=1 to amostras do f x done
28
29(** execução principal *)
30let () = time (eval tpk) ()
31
Gravar este programa no ficheiro tpkp.ml
. Compilar este programa com o compilador ocamlcp
(p de "profile"), usando o comando ocamlcp -P a tpkp.ml -o tpkp
.
Executar o programa perfilado tpkp
da forma usual. Por exemplo :
xxxxxxxxxx
11$ ./tpkp 1000000 11 800
Esta execução criou um ficheiro binário ocamlprof.dump
com todas as informações de execução que precisamos agora de recuperar e processar. É o objeto da alínea seguinte.
Usar o utilitário ocamlprof
que analisa um ficheiro ml
e junta a informação de perfil a partir do ficheiro ocamlprof.dump
.
xxxxxxxxxx
11$ ocamlprof tpkp.ml > tpkp_perfilado.ml
Analise o ficheiro tpkp_perfilado.ml
. Que parte do programa é o mais requisitado?
Execute o utilitário de geração de documentação disponível por omissão na distribuição OCaml, ocamldoc
(documentação). Usos mais avançados poderão passar, com muito proveito, por ferramentas como odoc
.
xxxxxxxxxx
11$ ocamldoc -html -keep-code tpkp.ml
Explore os ficheiros gerados com recurso ao seu browser (tpkp.html
, index.html
, etc.).
Vamos usar um dos muitos programas de testes existentes para programas OCaml
: o utilitário de fuzz-testing afl
para OCaml (documentação).
Instale o afl
para OCaml:
xxxxxxxxxx
11$ opam install afl
Considere o seguinte programa :
xxxxxxxxxx
121open Array
2let n = read_int ()
3let f t = Float.sqrt (Float.abs t) +. 5. /. (t -. 10.)
4let strange n = if n mod 3 = n mod 5 then n*2 else n-1
5let a = init n (fun _ -> read_float ())
6let () =
7 for i = strange n downto 0 do
8 let msg =
9 let y = f a.(i) in
10 if y <= 400. then (string_of_float y) else "TOO LARGE" in
11 print_endline msg
12 done
Grave este programa em fich.ml
e compile este programa por forma a que seja instrumentado pela ferramenta afl
:
xxxxxxxxxx
11$ ocamlopt -afl-instrument fich.ml -o strange
Prepare a execução de afl
:
xxxxxxxxxx
21$ mkdir input
2$ mkdir output
Coloque o ficheiro testecase
com o conteúdo
xxxxxxxxxx
12111
2-1
354
42
573
64
7100
8466
9400
1010000
11-76
1239
na pasta input
.
Execute a ferramenta afl
sobre o binário para a sua descoberta de inputs que causam quebra da execução :
xxxxxxxxxx
11$ afl-fuzz -i input -o output ./strange
Tenha em atenção que a ferramenta afl
vai gerar casos de teste a partir do exemplo dado (no ficheiro testcase
). A procura poderá demorar tempo. Quando o contador uniq crashes
apresentado na interface passar do uniq crashes : 1
, isso significará que a ferramenta encontrou um teste que provoca o fim abruto da execução. Neste caso aqui vários motivos para crashes
são detetados, Ctrl-c
.
Após o término da sua execução, analise os ficheiro presentes na pasta output
onde o afl
colocou o resultado da sua análise. Em particular os ficheiros presentes na pasta crashes
. Consegue perceber que falhas foram expostas?