Linguagens e Ambientes de Programação (2022/2023)



Prática 01

1ª parte: Primeiro contacto com a linguagem de programação OCaml e o seu ambiente de desenvolvimento na consola.
Exercícios de 1 a 8.

2ª parte: 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 9 e 10.



Ambiente de referência de LAP - Linux 64 bits

Nas aulas práticas o nosso ambiente de referência, tanto para trabalhar, como para os projetos, será o Linux instalado nos laboratórios (Ubuntu 16.04 - 64 bits). Destaque para o facto dos projetos de LAP serem corrigidos e avaliados no Linux dos laboratórios - o que significa que, no mínimo, é importante testar a versão final dos projetos no Linux, antes de submeter.

Ninguém está proibido de usar no seu computador pessoal o sistema operativo que quiser, e até existem versões para Windows e Mac de todo o software usado em LAP. Simplesmente, em LAP não é dado suporte a outros sistemas operativos, por exemplo para ajudar a resolver problemas de instalação (que por vezes se tornam particularmento complicados no Windows). Mas você poderá usar o fórum da disciplina para partilhar experiências com os seus colegas.



Ambiente de trabalho baseado na consola e no interpretador

Na a aula de hoje, a ideia é trabalhar numa consola, 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:

O mais prático, é arrastar para o desktop os ícones do Browser, Leafpad e LXTerminal.

O processo de desenvolvimento de programas OCaml é o seguinte:



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 do programa e o executa diretamente. Um compilador transforma o texto do programa numa linguagem mais simples para mais tarde ser executado numa máquina virtual ou diretamente no hardware.

Para instalar o OCaml no Ubuntu usa-se o comando:

Para executar o interpretador na linha de comando do Ubuntu pode usar-se o comando:

O interpretador não tem suporte para a "história" dos comandos introduzidos previamente, mas o comando rlwrap resolve o problema.

Nestes primeiros exercícios vamos fazer diversas experiências usando o interpretador de OCaml, e também uma pequena experiência usando o compilador de OCaml.


  • 1 - Numa consola Linux, lance o interpretador da linguagem OCaml usando o o comando rlwrap 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 e interpete os 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. (Para já não se preocupe com o tipo "unit", mas pode-se desde já dizer que é usado em muitas das situações em que o tipo "void" é usado em Java).

    Experimente isto:


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

    Usando um editor de texto, crie um ficheiro de texto chamado hello.ml, escreva o miniprograma (sim, só com uma linha)

    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. Num programa que se destine a ser compilado, 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 e anónimas. A definição de funções com nome e a sua invocação seguem a sintaxe que se exemplifica:

    Note que não se escrevem os tipos das entidades e 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:

    A nossa definição da função inicial f, também pode ser escrita assim:
  • 6 - As definições em OCaml podem ser recursivas, sendo para isso necessário indicar a palavra reservada rec. Reconhece esta função?
  • 7 - Experimente ainda as seguintes expressões para conhecer outras características da linguagem OCaml. Com a ajuda do professor, observe o tipo das funções e interprete os resultados que obtiver. Os principios aqui envolvidos serão alvo de discussão nas aulas teóricas.

    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 muito instrutivos pois ajudam a entender melhor as linguagens envolvidas e dos seus mecanismos. O esforço, nada trivial, de resolver este tipo de exercícios, 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. Aqui, a ideia é mesmo simular de alguma forma 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. Descobre-se que é possível simular esses mecanismos em OCaml. Contudo, como veremos já a partir da próxima semana, quando se resolve um problema em OCaml, não há necessidade, sendo até prejudicial, pensar em termos de variáveis imperativas e de ciclos. Neste caso, estamos perante um exercício académico de "imitação dos mecanismos de outra linguagem".
  • 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 cliente Canvas? [Sugestão: a melhor solução mantém as cinco classes, sendo a classe Shape que precisa duma grande reescrita.] Já agora, complete o código, pois faltam os construtores.


  • 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 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 disciplina de Teoria 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". Partindo do estado "START", navege mentalmente do diagrama para perceber como ele funciona. [Nota: há um detalhe que foi simplificado relativamente ao que se estuda em TC.]

    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 existe 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 ficar apenas a recursividade?

    Vídeos antigos

    Estes vídeos poderão estar um pouco desatualizados, pois foram feitos no contexto duma edição anterior do LAP. Contudo, partes dos vídeos poderão continuar a ter alguma utilidade.