Linguagens e Ambientes de Programação (2016/2017)



Prática 1

1ªhora: Primeiro contacto com a linguagem de programação OCaml e o seu ambiente de desenvolvimento na consola.
2ªhora: Exprimir ou aproximar os mecanismos disponíveis numa dada linguagem de programação numa outra linguagem que os não possua como primitivos.
Exercícios de 1 a 10.

Ambiente de referência - Linux

Nas aulas práticas o nosso ambiente de referência, tanto para trabalhar como para os projetos, será o Linux instalado nos laboratórios.


Primeiro contacto com a linguagem de programação OCaml e o seu ambiente de desenvolvimento na consola

A linguagem de programação OCaml é um dialeto da linguagem de programação ML.

Na 1ª parte da cadeira LAP, vamos explorar a vertente funcional da linguagem OCaml. Em OCaml, um programa é essencialmente uma sequência de definições de constantes e de definições de funções. Constantes e funções são conceitos já conhecidos da matemática. Note que numa linguagem funcional pura não estão disponíveis, nem variáveis mutáveis nem ciclos.

Um programa OCaml pode ser executado de duas formas distintas: (1) interpretado; ou (2) compilado e executado autonomamente. Um interpretador é um programa que lê o texto de um programa e o executa diretamente. Um compilador transforma o texto de um programa numa linguagem mais simples para mais tarde ser executado numa máquina virtual ou diretamente no hardware.

Nestes primeiros exercícios vamos fazer experiências com as duas vertentes de execução de um programa OCaml.


  • 1 - Numa consola Linux, lance o interpretador da linguagem OCaml usando o o comando ocaml. O interpretador aceita três tipos de entidades:

    Note que todas as entradas do interpretador são finalizadas com um ponto e vírgula duplo.

    Experimente introduzir o seguinte e observe oa resultados produzidos:

    Geralmente, é dado um valor como resposta, sendo ainda indicado o seu tipo e o identificador associado no caso de ser uma definição.

    Nota: Em vez da diretiva #quit;;, pode inserir CTRL-D no Linux ou CTRL-Z no Windows.


  • 2 - Para além da produção de valores, as expressões OCaml também podem originar ações de input/output. A escrita de valores no terminal é conseguida através da invocações de funções da biblioteca padrão do OCaml. Repare que existe um print diferente para cada tipo de valores.

    Experimente isto:


  • 3 - Neste exercício vamos abandonar temporariamente o interpretador de OCaml e usar o compilador de OCaml.

    Num ficheiro de texto chamado hello.ml, escreva o miniprograma

    e depois compile-o na linha de comando assim:

    Este comando produz um ficheiro executável chamado hello na diretoria corrente. Execute-o assim:

    Note que o interpretador escreve automaticamente o valor das expressões avaliadas na consola, mas o compilador não faz isso. Temos de ser nós a mandar escrever tudo usando as operações de input/output exemplificadas no exercício anterior.


  • 4 - Neste exercício regressamos ao interpretador, mas agora o código a executar provirá dum ficheiro e não da consola. Carregue o ficheiro "hello.ml" no interpretador através da diretiva "use":

    Observe o resultado obtido e tire conclusões.


  • 5 - Em OCaml há duas maneiras de definir funções: com nome ou anónimas. A definição de funções com nome e a sua invocação seguem a sintaxe que se exemplifica:

    Note que o interpretador de OCaml infere o seguinte tipo para a função: int -> int.

    A seguinte função anónima

    tem o mesmo tipo, mas não fica identificada por nenhum nome. É possível usar diretamente funções anónimas em expressões, como no exemplo:


  • 6 - As declarações em OCaml podem ser recursivas desde que precedidas da palavra rec. Conhece bem esta função, não é?
  • 7 - Experimente ainda as seguintes expressões para conhecer outras características da linguagem OCaml. Com a ajuda do professor, interprete os resultados que obtiver. Tudo isto será objeto de discussão nas aulas teóricas.

    Ambiente de trabalho baseado na consola e no interpretador

    Na primeira parte da aula de hoje, trabalhámos da forma que se descreve a seguir. Na próxima aula começaremos a usar o ambiente de trabalho Eclipse.

    Manter três janelas abertas, no computador:

    Para facilitar a criação destas três janelas no início de cada sessão, arraste para a barra que se situa no topo do ecrã os ícones das três aplicações relevantes: "Browser", "Editor de texto" e "Terminal".

    O processo de desenvolvimento de programas OCaml é o seguinte:



    Exprimir ou aproximar os mecanismos disponíveis numa dada linguagem de programação numa outra linguagem que os não possua como primitivos.

    Por vezes, quando estamos a trabalhar com uma linguagem L1, intessa-nos imitar um mecanismo de programação conhecido da linguagem L2 mas não disponível na linguagem L1. Exemplos: imitar as exceções do Java numa linguagem sem exceções; imitar os objetos do Java numa linguagem sem objetos; imitar o mecanismos de execução do Prolog numa linguagem sem esses mecanismos, etc.

    Este tipo de exercícios são instrutivos e ajudam a entender bem as linguagens envolvidas e dos seus mecanismos, pois o esforço de os resolver obriga a perceber completamente o mecanismo da linguagem L2 que se quer imitar, assim como os mecanismos da linguagem L1 que são usados na imitação.

    Segue-se alguns exercícios deste género.


  • 8 - Dada a ausência de variáveis imperativas e de ciclos na parte funcional da linguagem OCaml, proponha uma função recursiva em OCaml para implementar uma função equivalente ao seguinte método Java. A ideia é mesmo simular variáveis imperativas em OCaml.

    A função calcula o máximo divisor comum entre dois números inteiros positivos.

    Nota: Neste exercício, o ponto de partida foi um método escrito usando os mecanismos imperativos do Java. Naturalmente fomos levados a simular esses mecanismos em OCaml. Contudo, como veremos nas próximas semanas, quando se resolve um problema diretamente em OCaml, não há qualquer necessidade nem interesse, sendo mesmo prejudicial pensar em termos de variáveis imperativas e de ciclos.
  • 9 - Considere o seguinte programa em Java: Como você sabe, uma variável de tipo Shape consegue referir objetos de tipo Line, Circle e Rect. Ao aplicar a operação "area" a essa variável, obtém-se a área da figura geométrica em causa.

    Agora, imagine que eram retirados do Java os mecanismos de herança e subtipo, que aqui são usados. Como é que reescreveria o programa anterior, tentando capturar o conceito da classe "Shape" (que representa uma forma geométrica geral) e, já agora, sem alterar a classe Canvas? [Sugestão: a melhor solução mantém 5 classes, sendo a classe Shape que precisa duma maior reescrita.]


  • 10 - Em Java não existe a instrução goto (apesar desta palavra ter ficado reservada nessa linguagem). Em geral o goto não faz falta, e na maioria das situações até seria contraproducente o seu uso por isso favorecer o aparecimento de programas difíceis de entender [Artigo: Edsger W. Dijkstra, Go To Statement Considered Harmful, 1969].

    Mas há uma situação rara em que o uso do goto é recomendado, se estiver disponível. Trata-se da programação de autómatos finitos deterministas (máquinas de estados). Num tal autómato, para cada par (estado, input) existe uma única transição para o estado seguinte. Você vai estudar este assunto na disciplica de da Computação.

    O seguinte diagrama representa um autómato finito com 5 estados que verifica se o input contém, algures lá no meio, a string "foo".

    O programa mais abaixo, implementa em C o autómato anterior. Olhe para o código e repare que o goto permite transitar de estado de forma natural e intuitiva.

    O problema a resolver é o seguinte: Escreva um programa em Java equivalente ao programa em C e teste-o.

    O Java não tem instrução goto. Qual será o melhor plano para efetuar a tradução? Será que existe alguma solução que permita manter a estrutura geral do código C que estamos a ver. [A resposta é sim! Se fosse não, então o goto faria falta em Java.]]

    Para ler carateres, pode usar o seguinte método, que imita a função getchar do C:
  • 11 - Imagine que o mecanismo das exceções era retirado da linguagem Java. Reescreva o seguinte programa Java, mantendo o seu comportamento, mas agora sem usar exceções. A solução que encontrar deverá poder ser aplicada a qualquer outro programa Java. Verá que há uma solução teoricamente correta, mas muito pouco prática de usar: isso prova que o mecanismo das exceções faz falta no Java, pois não há forma simples de o imitar usando os outros mecanismos. Ajuda: Pode usar a seguinte classe enumerada auxiliar. Há uma solução baseada em ideias simples que passa por inserir no código alguns ifs, alguns returns, criar uma nova função main com código fixo, e pouco mais. Mesmo assim é uma solução complicada de usar, na prática.
  • 12 - Reescreva o método Java abaixo sem usar nenhum ciclo. Use apenas recursividade. Depois de resolver o problema diga se aceitável eliminar os ciclos do Java, deixando apenas a recursividade?