Baixe o app para aproveitar ainda mais
Prévia do material em texto
AUTARQUIA EDUCACIONAL DO VALE DO SÃO FRANCISCO – AEVSF FACULDADE DE CIÊNCIAS APLICADAS E SOCIAIS DE PETROLINA – FACAPE ADRIEL SIQUEIRA BENTO ESTUDO E AVALIAÇÃO DA LINGUAGEM DE PROGRAMAÇÃO SCALA E SEU USO NA PROGRAMAÇÃO PARALELA PETROLINA – PE 2016 2 ADRIEL SIQUEIRA BENTO ESTUDO E AVALIAÇÃO DA LINGUAGEM DE PROGRAMAÇÃO SCALA E SEU NA PROGRAMAÇÃO PARALELA Trabalho obrigatório de conclusão de curso para obtenção de grau de bacharel em Ciência da Computação apresentado a Faculdade de Ciências Aplicadas e Sociais de Petrolina. Orientador: Prof. Me. Jocélio de Oliveira Dantas Passos. PETROLINA – PE 2016 3 ADRIEL SIQUEIRA BENTO ESTUDO E AVALIAÇÃO DA LINGUAGEM DE PROGRAMAÇÃO SCALA E SEU NA PROGRAMAÇÃO PARALELA Trabalho obrigatório de conclusão de curso para obtenção de grau de bacharel em Ciência da Computação apresentado a Faculdade de Ciências Aplicadas e Sociais de Petrolina. Orientador: Prof. Me. Jocélio de Oliveira Dantas Passos. Aprovada em: ___/___/______. BANCA EXAMINADORA ________________________________________ Prof. Me. Jocélio de Oliveira Dantas Passos. (Orientador) Faculdade de Ciências Aplicadas e Sociais de Petrolina. _________________________________________ Prof. Me. Carlos Alberto Teixeira Batista Faculdade de Ciências Aplicadas e Sociais de Petrolina. _________________________________________ Prof. Esp. Thomas de Almeida Rabelo Faculdade de Ciências Aplicadas e Sociais de Petrolina. 4 “O Temor do Senhor é o princípio do conhecimento, mas os loucos desprezam a sabedoria e a instrução.” – Provérbios 1:7 “Porque o Senhor dá a sabedoria, da Sua boca vem o conhecimento e o entendimento.” – Provérbios 2:6 5 AGRADECIMENTOS Primeiro ao nosso Amoroso e Fiel Pai, que é Deus Eterno e Todo Poderoso, O Criador e Arquiteto de tudo que existe e é bom. Por ter estado ao meu lado me protegendo de vários males durante todo o curso de graduação. A Jesus Cristo, o Seu Filho e meu Salvador, pelo Seu exemplo que me ensina e motiva a não esquecer dos outros enquanto se corre atrás de objetivos próprios, e ao Senhor Deus Espirito Santo, que me consolou e animou nos momentos difíceis durante toda minha vida. Minha principal e primeira fonte de sabedoria, de conhecimento, e dos bons resultados. Agradeço a minha Mãe Selma Bento, por seu amor terno, por suas cobranças que me despertavam e me faziam ser mais atento. Pela prontidão em me apoiar com o que fosse necessário tanto em conselhos como em material. Também ao meu Pai Hermes Siqueira Cavalcante, pelo ombro amigo e preocupado que sempre me ofereceu mesmo em meio a muito trabalho. Suas orações de Pai moviam o braço do Bom Deus em minha guia e ajuda. Seu apoio financeiro sempre que precisei também me ajudou Também e em especial a minha namorada Aísla Gomes Guimarães por ter estado sempre ao meu lado a me apoiar de verdade, por muitas vezes ter se prontificado e ter oferecido mais ajuda do que eu poderia aceitar, por ter sempre orado por mim. Seu apoio me foi e sempre será de inestimável valor. Agradeço muito pela paciência e acompanhamento do Prof. Me. Jocélio Dantas Passos, pois não só no lado acadêmico mas também com suas orientações e exemplo me ensinou a ser mais equilibrado nas várias obrigações que um Homem leva em suas costas. Também agradeço ao meu amigo Eduardo Fernandes por sua amizade verdadeira em frequente ajuda, seus incentivos sinceros em minha vida estudantil e de trabalho. Obrigado a todos os meus amigos e professores pelos anos juntos, incentivo e companhia especial de cada um de vocês. 6 RESUMO Muito já se falara sobre o paradigma de programação funcional e suas vantagens e desvantagens em relação ao paradigma orientado a objeto, porém a complexidade do paradigma funcional, e grandes diferenças em relação ao paradigma orientado a objeto, foram um entrave a sua popularidade no mercado do desenvolvimento de software. Em 2001 Martin Odersky, co-criador do Generic java, e um dos implementadores da JVM (Máquina Virtual Java) liderou a criação da linguagem de programação Scala, que veio a ganhar mais fama a partir de sua versão 2.0 lançada em 2003. A linguagem Scala traz o sonho do paradigma funcional para mais perto do mercado de desenvolvimento de software, pois ao passo que se utiliza do paradigma funcional possibilita a integração com o paradigma orientado a objeto, com o qual também trabalha. Juntando ambos os paradigmas sem desqualificar os desenvolvedores acostumados com o desenvolvimento orientado a objeto ou funcional. O presente trabalho avalia a linguagem Scala e seus principais recursos. Ao fim, ver-se-á que Scala pode ser usada tanto na programação funcional quanto orientada a objeto. Palavras-chave: linguagem Scala, funcional, orientado a objeto, JVM. 7 ABSTRACT Much has been spoken about the functional programming paradigm and its advantages and disadvantages compared to object-oriented paradigm, but the complexity of the functional paradigm, and large differences from the object-oriented paradigm, were an obstacle to its popularity in the market development software. In 2001 Martin Odersky, co-creator of Generic java, and one of the implementers of the JVM (Java Virtual Machine) led the creation of the Scala programming language, which later gained more fame from its 2.0 version launched in 2003. The Scala brings the dream of the functional paradigm closer to the software development market because while using the functional paradigm enables integration with object-oriented paradigm, with which also works. Joining both paradigms without disqualifying the developers used to develop object-oriented or functional. This study evaluates the Scala language and its key features. In the end, it will be seen that Scala could be used either in functional programming and object-oriented. Key words: Scala language, functional, object-oriented, JVM. 8 LISTA DE FIGURAS Figura 1 – Processo de compilação...........................................................................................26 Figura 2 – Processo de interpretação........................................................................................27 Figura 3 – Importação com Scala.............................................................................................30 Figura 4 – Interpretador REPL..................................................................................................37 Figura 5 – Erro de interpretação...............................................................................................37 Figura 6 – Inferência do “;”......................................................................................................37 Figura 7 – Expressões simples..................................................................................................39 Figura 8 – Values criados pelo REPL........................................................................................39 Figura 9 – Validação de values..................................................................................................40 Figura 10 – Valores imutáveis...................................................................................................41 Figura 11 – Validação call-by-value..........................................................................................42 Figura 12 – Validação call-by-name.........................................................................................43 Figura 13 – Exemplo If-else em Scala......................................................................................44 Figura 14 – Recursividade comum...........................................................................................45Figura 15 – Recursão de cauda.................................................................................................46 Figura 16 – Função anônima.....................................................................................................50 Figura 17 – Uso de somaInts, somaCubos e somaFatoriais......................................................51 Figura 18 – Pilha genérica........................................................................................................57 Figura 19 – Aplicação de simPrefixo........................................................................................58 Figura 20 – Traits implementadas.............................................................................................63 Figura 21 – Combinando abstrações.........................................................................................64 Figura 22 – Coleções: mutáveis e imutáveis.............................................................................66 Figura 23 – Operações simples.................................................................................................67 Figura 24 – Testando conjuntos (sets).......................................................................................69 Figura 25 – Testando mapas (maps)..........................................................................................71 Figura 26 – Iteração com coleções............................................................................................73 Figura 27 – Implementando geradores e filtros........................................................................75 Figura 28 – Primeira sintaxe do for...........................................................................................76 Figura 29 – Segunda sintaxe do for...........................................................................................77 Figura 30 – Seleções de campo e método.................................................................................79 Figura 31 – Flexibilidade entre coleções..................................................................................86 Figura 32 – Definições e tipos de dados...................................................................................87 Figura 33 – Combinando coleções............................................................................................92 Figura 34 – Flexibilidade da inferência....................................................................................94 Figura 35 – Inferência em tipos genéricos................................................................................95 Figura 36 – Mapeamento em paralelo.....................................................................................102 Figura 37 – Filtrando em paralelo...........................................................................................103 Figura 38 – Instanciando ParArray.........................................................................................105 Figura 39 – Exemplo de ParVector.........................................................................................106 Figura 40 – Implementando ParHashMap..............................................................................107 Figura 41 – Implementado ParHashSet..................................................................................108 9 LISTA DE QUADROS Quadro 1 – Incrementação em C...............................................................................................18 Quadro 2 – Exemplos para o operador '+'.................................................................................19 Quadro 3 – Tipo numérico fazendo papel booleano.................................................................20 Quadro 4 – Tipo booleano.........................................................................................................20 Quadro 5 – Facilidade de escrita...............................................................................................22 Quadro 6 – Definições em Scala...............................................................................................40 Quadro 7 – Chamada de argumentos call-by-value..................................................................42 Quadro 8 – Função loop............................................................................................................43 Quadro 9 – Chamada de argumentos call-by-name..................................................................44 Quadro 10 – Validação da fatoração.........................................................................................45 Quadro 11 – Validação com recursão de cauda.........................................................................46 Quadro 12 – Funções comuns...................................................................................................48 Quadro 13 – Funções de ordem superior..................................................................................48 Quadro 14 – Função anônima mais enxuta...............................................................................50 Quadro 15 – Função currying...................................................................................................51 Quadro 16 – Sintaxe especial para currying.............................................................................52 Quadro 17 – Casamento de padrões..........................................................................................53 Quadro 18 – Casamento de padrões e classes case...................................................................55 Quadro 19 – Tipo genérico........................................................................................................56 Quadro 20 – Método genérico..................................................................................................57 Quadro 21 – Função divMod....................................................................................................59 Quadro 22 – Biblioteca Scala: Tuple2......................................................................................60 Quadro 23 – Instâncias de Tuple2.............................................................................................60 Quadro 24 – Acesso a tuplas.....................................................................................................61 Quadro 25 – Tuplas e casamento de padrões............................................................................61 Quadro 26 – Trait parcialmente implementada.........................................................................62 Quadro 27 – Operação com coleções........................................................................................65 Quadro 28 – Implementação de listas.......................................................................................68 Quadro 29 – Elementos fundamentais......................................................................................69 Quadro 30 – Formato expressões for.........................................................................................74 Quadro 31 – Implementando geradores....................................................................................75 Quadro 32 – Sintaxe com elemento incomum..........................................................................77 Quadro 33 – Keywords do Scala...............................................................................................79 Quadro 34 – Implementações de for.........................................................................................80 Quadro 35 – Implementações de currying................................................................................80 Quadro 36 – Conceitos simples................................................................................................81 Quadro 37 – Acoplamento ortogonal de conceitos...................................................................82Quadro 38 – Estrutura do if em Scala.......................................................................................83 Quadro 39 – Estrutura do if em Java.........................................................................................83 Quadro 40 – Estrutura do match Scala......................................................................................83 Quadro 41 – Estrutura do switch Java.......................................................................................84 Quadro 42 – Outra estrutura match Scala.................................................................................84 Quadro 43 – Estrutura do case em Haskell...............................................................................84 Quadro 44 – Classes como tipo de dado...................................................................................87 Quadro 45 – Keywords comuns Java e Scala............................................................................88 Quadro 46 – Keywords comuns Python e Scala........................................................................88 Quadro 47 – Comparação de sintaxe Java e Scala....................................................................89 10 Quadro 48 – Expressividade com padrões de correspondência................................................92 Quadro 49 – Lançamento de exceção.......................................................................................96 Quadro 50 – Try-catch-finally em Java.....................................................................................96 Quadro 51 – Try-catch-finally em Scala...................................................................................97 Quadro 52 – Exemplo de mnemônico com Scala.....................................................................98 Quadro 53 – Mapeando uma lista...........................................................................................101 Quadro 54 – Mapeando uma lista em paralelo.......................................................................101 Quadro 55 – Importação e instanciação de coleção paralela..................................................103 Quadro 56 – Conversão de sequencial para paralelismo........................................................104 11 LISTA DE TABELAS Tabela 1 – Operadores aritméticos............................................................................................31 Tabela 2 – Operadores relacionais............................................................................................31 Tabela 3 – Operadores lógicos..................................................................................................32 Tabela 4 – Operadores bit a bit.................................................................................................32 Tabela 5 – Operadores de atribuição.........................................................................................33 Tabela 6 – Precedência de operadores......................................................................................35 Tabela 7 – Operações básicas....................................................................................................67 Tabela 8 – Coleções sequenciais e paralelas...........................................................................109 12 LISTA DE ABREVIATURAS E SIGLAS VB – Visual Basic JVM – Java Virtual Machine CMD – Command Prompt REPL – Read-eval-print-loop Interpreter API – Application Programming Interface SIP – Scala Improvement Process JIT – Just-in-time Compiler 13 SUMÁRIO 1 INTRODUÇÃO.....................................................................................................................15 1.1 Justificativa......................................................................................................................16 1.2 Objetivo geral...................................................................................................................16 1.3 Objetivos específicos.......................................................................................................16 1.4 Metodologia.....................................................................................................................16 2 CRITÉRIOS DE AVALIAÇÃO.............................................................................................17 2.1 Legibilidade.....................................................................................................................18 2.1.1 Simplicidade Global................................................................................................18 2.1.2 Ortogonalidade........................................................................................................19 2.1.3 Estruturas de Controle.............................................................................................20 2.1.4 Tipos de Estruturas de Dados..................................................................................20 2.1.5 Projeto de Sintaxe...................................................................................................20 2.2 Facilidade de Escrita........................................................................................................21 2.2.1 Simplicidade e Ortogonalidade...............................................................................21 2.2.3 Expressividade........................................................................................................22 2.3 Confiabilidade..................................................................................................................22 2.3.1 Tipagem da Linguagem...........................................................................................23 2.3.2 Manipulação de Exceções.......................................................................................23 2.3.3 Apelidos (Aliases)...................................................................................................23 2.4 Portabilidade....................................................................................................................24 2.5 Generalidade....................................................................................................................24 2.6 Compilação......................................................................................................................25 2.7 Interpretação Pura............................................................................................................27 3 LINGUAGEM SCALA.........................................................................................................28 3.1 Histórico...........................................................................................................................28 3.2 Características técnicas....................................................................................................29 3.3 Operadores.......................................................................................................................30 3.3.1 Operadores Aritméticos...........................................................................................31 3.3.2 Operadores Relacionais...........................................................................................31 3.3.3 Operadores Lógicos................................................................................................32 3.3.4 Operadores Bit a Bit................................................................................................32 3.3.5 Operadores de Atribuição........................................................................................33 3.3.6 Precedência de Operadores.....................................................................................353.4 Ambiente de Testes..........................................................................................................36 3.5 Expressões e Funções Simples.........................................................................................38 3.6 Valores Imutáveis.............................................................................................................40 3.7 Parâmetros........................................................................................................................41 3.8 Expressão Condicional If-else..........................................................................................44 3.9 Recursão de Cauda (Tail Recursion)................................................................................45 3.10 Funções de Ordem Superior (High-Order Functions)...................................................47 3.11 Funções Anônimas.........................................................................................................49 3.12 Currying.........................................................................................................................50 3.13 Casamento de Padrões (Pattern Matching)...................................................................52 3.14 Classes e objetos case (Case Classes and Case Objects)..............................................54 3.15 Tipos Genéricos e Métodos (Generic Types and Methods)...........................................56 3.16 Tuplas (Tuples)...............................................................................................................59 3.17 Traços (Traits)................................................................................................................61 14 3.18 Coleções (Collections)...................................................................................................65 3.18.1 Listas (Lists)..........................................................................................................67 3.18.2 Conjuntos (Sets)....................................................................................................69 3.18.3 Mapa (Map)...........................................................................................................70 3.19 Iteradores (Iterators)......................................................................................................71 3.20 Estruturas de controle for e suas expressões..................................................................73 4 AVALIAÇÃO DA LINGUAGEM.........................................................................................78 4.1 Legibilidade.....................................................................................................................78 4.1.1 Simplicidade Global................................................................................................78 4.1.2 Ortogonalidade........................................................................................................81 4.1.3 Estruturas de Controle.............................................................................................82 4.1.4 Tipos e estruturas de dados.....................................................................................85 4.1.5 Projeto de Sintaxe...................................................................................................88 4.2 Facilidade de Escrita........................................................................................................90 4.2.1 Simplicidade e Ortogonalidade...............................................................................90 4.2.2 Suporte a abstração.................................................................................................91 4.2.3 Expressividade........................................................................................................91 4.3 Confiabilidade..................................................................................................................93 4.3.1 Tipagem da linguagem............................................................................................93 4.3.2 Manipulação de Exceções.......................................................................................95 4.3.3 Apelidos (Aliases)...................................................................................................97 4.4 Portabilidade e Generalidade...........................................................................................98 4.5 Implementação da Linguagem.........................................................................................99 4.6 Conclusão da Avaliação.................................................................................................100 5 PROGRAMAÇÃO COM COLEÇÕES PARALELAS.......................................................100 5.1 Simplicidade na paralelização........................................................................................101 5.2 Exemplos de implementação.........................................................................................102 5.3 Criando uma coleção paralela........................................................................................103 5.4 Alguns tipos de coleções paralelas (mutáveis e imutáveis)...........................................104 5.4.1 ParArray (Matriz Paralela)....................................................................................104 5.4.2 ParVector (Vetor Paralelo)....................................................................................105 5.4.3 Mutable ParHashMap (Mapa Hash Paralelo mutável).........................................106 5.4.4 Mutable ParHashSet (Conjunto Hash Paralelo mutável)......................................107 5.5 Conversão de coleções – Métodos par e seq..................................................................108 6 CONCLUSÃO E TRABALHOS FUTUROS......................................................................109 7 REFERÊNCIAS...................................................................................................................111 15 1 INTRODUÇÃO Em Ciência da computação um paradigma de programação refere-se há um modelo, padrão ou estilo de programação suportado por linguagens de programação que agrupam certas características comuns (SÁ; SILVA, 2006). Como exemplos de paradigmas de programação tem-se o Funcional e o Orientado a Objeto (SÁ; SILVA, 2006). O paradigma orientado a objeto visa dar a uma linguagem de programação o poder de simular o mundo real de forma abstrata, eficiente e eficaz. “Muitas linguagens de programação desenvolvidas a princípio em o paradigma imperativo hoje suportam a orientação a objeto” (SEBESTA, 2010). “Por sua vez, a programação funcional está se tornando cada vez mais popular por oferecer métodos atrativos de explorar o paralelismo em arquiteturas multicore e em computação nas nuvens” (ODERSKY, 2014). No senso mais restrito a programação funcional significa programar sem fazer uso de variáveis mutáveis, atribuições, loops, e outras estruturas de controle da programação imperativa. Enquanto em um senso mais amplo significa um foco em funções, pois aqui, elas podem atuar como valores que são produzidos, consumidos e compostos. “Tudo isto porém, se torna fácil de fazer em uma linguagem de programação funcional” (ODERSKY, 2012). Levando em consideração estas características apresentadas, Scala (Linguagem de Programação Escalável) integra facilmente a programação orientada a objetos à funcional. Ela é projetada para expressar os padrões de programação comuns delesde forma concisa, elegante e tipada (segurança de tipos) (TYPESAFE, 2015), e ao mesmo tempo, Scala é compatível com Java (ODERSKY, 2014). Funciona com a mesma máquina virtual. Bibliotecas e frameworks Java podem ser usados em código Scala tal como estão, isto é, sem código ou declarações adicionais (ODERSKY, 2014). Scala também facilita a programação paralela de código, permitindo que um computador com arquitetura multicore (ou um conjunto de computadores conectados) divida o código e processe várias partes dele ao mesmo tempo (SCHEPKE, 2009) (SOBRAL, 2016). 16 1.1 Justificativa A pouca existência de material traduzido, de comunidades de desenvolvimento Scala (em comparação a outras linguagens de programação) e alto nível de complexidade. Com vista nisso o presente trabalho familiarizará o interessado com a linguagem Scala e algumas das principais características dela. Dessa forma será mais fácil decidir empenhar-se ou não em se aprofundar nela, posto a presente falta de material traduzido. 1.2 Objetivo geral Avaliar a linguagem Scala com base nos critérios obtidos da literatura acadêmica, apresentando algumas de suas características principais e uma de suas capacidades para a programação paralela. 1.3 Objetivos específicos Pesquisar critérios de avaliação na literatura acadêmica. Avaliar a linguagem Scala a partir de tais critérios. Apresentar as coleções paralelas da linguagem para a programação paralela. 1.4 Metodologia Pesquisar em trabalhos e leituras científicas critérios de avaliação adequados para a linguagem Scala. Estudar características dentre as principais da linguagem, de maneira que uma facilite a compreensão da outra, até que, por fim, convenha apresentar as coleções paralelas. Utilizar exemplos de código para avaliar a linguagem Scala a partir dos 17 critérios levantados. Apresentar através de exemplos de código como a programação paralela é implementada pela linguagem. Os próximos capitulos estarão dispostos da seguinte forma: (2) Critérios de Avaliação: levatamento de critérios de avaliação para a referida linguagem de programação; (3) Apresentação da linguagem e suas caracteristicas notaveis; (4) Avaliação da linguagem Scala: resultado da avaliação com base nos critérios levantados; (5) Estudo das coleções paralelas da biblioteca padrão da linguagem para a programação paralela; (6) Conclusão e Trabalhos Futuros. 2 CRITÉRIOS DE AVALIAÇÃO “Usam-se critérios de avaliação em linguagens de programação para descobrir como os recursos de uma linguagem estão influenciando no processo de desenvolvimento e manutenção de softwares feitos com esta linguagem” (SEBESTA, 2010). Não é sempre possível fazer com que dois desenvolvedores de software experientes concordem a respeito do mesmo critério em uma determinada linguagem. Mas ainda assim os critérios têm seu valor (SEBESTA, 2010). Os critérios escolhidos para a avaliação foram os abordados pelo autor e professor Robert W. Sebesta em seu livro “Conceitos de Linguagens de Programação” (SEBESTA, 2010), com exceção do critério de custo, que não foi incluído no trabalho para reduzir o tamanho do mesmo, os demais critérios são: Simplicidade Global, Ortogonalidade, Estruturas de Controle, Tipos de Estruturas de Dados, Projeto de Sintaxe, Simplicidade e Ortogonalidade, Suporte a Abstração, Expressividade, Tipagem, Manipulação de Exceções, Uso Seguro de Apelidos, Portabilidade e Generalidade. O critério de implementação, que foi utilizado no trabalho de conclusão de curso do aluno Francisco Adelanio Soares da Silva (2014) na FACAPE (Faculdade de Ciências Sociais e Aplicadas de Petrolina), é utilizado para enriquecer a avaliação da linguagem Scala. 18 Uma nota importante sobre critérios de avaliação: os critérios de projetos de linguagens apresentados a seguir tem diferentes pesos quando vistos de diferentes perspectivas. Projetistas de linguagens são propensos a enfatizar a elegância e a habilidade de atrair um grande número de usuários. Já os implementadores de linguagens estão preocupados principalmente com a dificuldade de implementar as construções e recursos da linguagem. Os usuários desenvolvedores por sua vez, estão preocupados primeiramente com a facilidade de escrita e depois com a legibilidade, enquanto analistas tem como anelo a legibilidade do código antes de sua facilidade de escrita. “Todas essas características geralmente entram em conflito” (SEBESTA, 2010). 2.1 Legibilidade Quão facilmente um programa pode ser lido e entendido. O valor da legibilidade em um código é sentido quando se faz necessária alguma manutenção no software, fato que acontece constantemente no mercado de desenvolvimento de software. O Critério de legibilidade agrega os subcritérios de: 2.1.1 Simplicidade Global Para ser simples e ter melhor aprendizado, uma linguagem precisa ter uma quantidade pequena de construções básicas. Precisa também possuir poucas maneiras para realizar uma mesma operação, como por exemplo, incrementar um contador. Observe o quadro 1. Quadro 1 – Incrementação em C. contador = contador + 1 contador += 1 contador++ ++contador Fonte: SEBESTA, 2010. Outro problema em potencial é a sobrecarga de operadores, ou seja, um único 19 símbolo (seja um operador de comparação, de atribuição, ou outro similar) possuir vários significados no código. Pode até parecer útil, mas o é apenas na fase de desenvolvimento, pois no momento em que alguém, que não seja o desenvolvedor, ler o código, sentirá dificuldades em relação a legibilidade do código. 2.1.2 Ortogonalidade Ortogonalidade diz respeito a capacidade da linguagem de programação permitir ao desenvolvedor combinar seus conceitos básicos sem que isto produza efeitos anômalos (efeitos anormais) (MATTOS, 2015). A ortogonalidade está fortemente relacionada a legibilidade e simplicidade do código. Linguagens de programação ortogonais são interessantes porque o programador pode prever, com segurança, o comportamento de uma determinada combinação de conceitos. Isso pode ser feito sem que se tenha de implementar teste para a averiguação do uso combinado de dois ou mais conceitos, ou mesmo buscar na especificação da linguagem de programação se existe alguma restrição àquela combinação. A falta de ortogonalidade diminui o aprendizado da linguagem de programação e pode estimular a ocorrência de erros de programação. (MATTOS, 2015) Como outro exemplo de ortogonalidade, imagina-se um projeto de linguagem onde o operador '+' possa ser usado tanto em operações de tipos numéricos como em concatenações. Isto traz simplicidade, facilita o trabalho do desenvolvedor, como mostra o exemplo do quadro 2. Quadro 2 – Exemplos para o operador '+'. a+b // Onde a e b são valores inteiros 78.056 + 23 + “F” “Samuel se encontra na ” + “Padaria.” Fonte: Autor. Porém há momentos em que isto não convém, por exemplo, caso a = “Samuel se encontra na ” e b = “Padaria.” o leitor do código não saberia que se tratam de strings (supondo que não houvesse nenhuma anotação na linha do código) a não ser que ele fosse estudar mais a fundo o código. O excesso de ortogonalidade pode levar a situações tais. 20 2.1.3 Estruturas de Controle A existência de instruções para controle de fluxo que já estejam bem conhecidas em outras linguagens de mesmo paradigma(s). 2.1.4 Tipos de Estruturas de Dados Facilidades para definir tipos e estruturas de dados. Como exemplo suponha que, devido a falta do tipo booleano em uma linguagem,o tipo numérico precise ser atribuído a uma variável que deve armazenar uma informação booleana, observe o quadro 3. Quadro 3 – Tipo numérico fazendo papel booleano. timeOut = 1 Fonte: SEBESTA, 2010. O significado dessa sentença não estará claro para alguém (não sendo o desenvolvedor) conseguir entender a primeira vista. Por outro lado o exemplo no quadro 4 é uma sentença perfeitamente clara. Quadro 4 – Tipo booleano. timeOut = true Fonte: SEBESTA, 2010. 2.1.5 Projeto de Sintaxe A sintaxe dos elementos de uma linguagem vai influenciar e muito na legibilidade do programa. De forma geral, para uma boa sintaxe, os identificadores devem possuir um tamanho moderado, nem muito comprido (pois pode implicar em erros de digitação), nem muito curto (prejudica a legibilidade). A linguagem também não deve permiti o uso de suas palavras reservadas como identificadores de variáveis, por isto atrapalhar a legibilidade do 21 código tanto pelos desenvolvedores e analistas como pelo compilador e/ou interpretador. As palavras reservadas de uma linguagem devem ter suas formas bem definidas e de modo a sugerir claramente o que elas fazem. 2.2 Facilidade de Escrita Mede o quão facilmente uma linguagem pode ser usada pelo desenvolvedor para criar um software para alguma aplicação. A maioria das características de linguagem que afetam a legibilidade também afetam a facilidade de escrita justamente pelo desenvolvedor constantemente ter que reler o código anteriormente já feito. Para avaliar a facilidade de escrita é importante levar em consideração o problema alvo para qual aquela linguagem de programação foi desenvolvida. Como exemplo disto Robert W. Sebesta (2010, p. 33) cita: … as facilidades de escrita do Visual BASIC (VB) e do C são drasticamente diferentes para criar um programa com uma interface gráfica com o usuário, para o qual VB é ideal. Suas facilidades de escrita também são bastante diferentes para a escrita de programas de sistema, como um sistema operacional, para os quais a linguagem C foi projetada. 2.2.1 Simplicidade e Ortogonalidade Os critérios de simplicidade e ortogonalidade precisam aqui ser combinados e equilibrados. “Poucos construtores, um pequeno número de primitivas, um pequeno conjunto de regras para combiná-los” (BOECHAT, 2016). Segundo Robert W. Sebesta (2010, p. 33) … muita ortogonalidade pode prejudicar a facilidade de escrita. Erros em programas podem passar despercebidos quando praticamente quaisquer combinações de primitivas são legais. Isso pode levar a certos absurdos no código que não podem ser descobertos pelo compilador. 22 2.2.2 Suporte a Abstração Abstração é a capacidade de definir e de usar estruturas ou operações complexas de maneira que permita ignorar muitos dos detalhes. O grau de abstração permitido por uma linguagem de programação e a facilidade que o desenvolvedor tem de expressar o que deseja nela são importantes para sua facilidade de escrita. O uso de módulos prontos para determinado algoritmo que precisa ser usado várias vezes no programa também é considerado uma forma de abstração (abstração de processo). 2.2.3 Expressividade Em geral para uma linguagem ter boa expressividade ela deve permitir que o desenvolvedor especifique códigos de uma forma conveniente, e não deselegante, diminuindo assim o número de linhas de código. Devem possuir construções que aumentem a sua facilidade de escrita, tais como as presentes no quadro 5. Quadro 5 – Facilidade de escrita. • count++ é mais conveniente do que count = count + 1 • a inclusão do for em muitas linguagens modernas Fonte: SEBESTA, 2010. A maioria das linguagens de alto nível apresentam melhor expressividade se comparadas com linguagens de baixo nível, pois são desenvolvidas visando, principalmente, facilitar o trabalho do desenvolvedor de software. 2.3 Confiabilidade Vale ressaltar aqui que a legibilidade do código afeta a confiabilidade de um programa, tanto na escrita quanto na manutenção (SEBESTA, 2010). O critério de confiabilidade diz respeito ao programa escrito com a linguagem de programação. Um software pronto é dito confiável se ele estiver de acordo com todas as suas especificações em 23 todas as condições. Para uma melhor confiabilidade, também faz-se necessário que a linguagem de programação escolhida lide bem com os seguintes subtópicos. 2.3.1 Tipagem da Linguagem Os tipos de dados definidos na implementação de um código precisam ser testados para descobrir se houve erros de tipo (SEBESTA, 2010). Como a verificação de tipos em tempo de execução é cara, a verificação em tempo de compilação é mais desejável, sem falar que quanto mais cedo os erros no programa forem encontrados e corrigidos menor será o custo com reparos necessários (SEBESTA, 2010). Exemplo e prova disto aconteceu em 4 de junho de 1996, onde em menos de um minuto após seu lançamento o foguete francês Ariane 501 (projeto da Agência Espacial Europeia) se autodestruiu (ARIANE… 1996). Após as investigações feitas por uma comissão presidida pelo matemático francês Jacques Louis Lions, do Colégio de França, foi indicado um erro numérico (overflow) no software de controle onde um programa que convertia um valor em ponto flutuante para um inteiro de 16 bits recebeu como entrada um valor que estava fora da faixa permitida e isto causou um shut-down nos computadores principal e reserva ao mesmo tempo, resultando a terrível falha no lançamento. 2.3.2 Manipulação de Exceções É a capacidade de perceber, interceptar e tratar erros em tempo de execução oferecida por uma linguagem de programação. Como exemplos de linguagens que fazem o tratamento de exceções temos Ada, C++ e Java. Mas tais facilidades são praticamente inexistentes em muitas linguagens amplamente usadas, como C e Fortran. 2.3.3 Apelidos (Aliases) Apelidos (aliases) são referências a endereços da memória principal; 24 mnemônicos, ponteiros, variáveis, objetos, constantes são alguns exemplos de apelidos. O uso de apelidos pode ser algo perigoso em uma linguagem de programação, isto ocorre quando é possível ter um ou mais nomes para acessar a mesma célula de memória, por exemplo, dois ponteiros configurados para apontarem para o espaço de memória de uma mesma variável. A maioria das linguagens de programação permitem algum tipo de apelido. Na linguagem C os apelidos são implementados através de ponteiros, em Java através de objetos. Em algumas linguagens apelidos são usados para resolver deficiências nos recursos de abstração de dados; outras restringem o uso de apelidos para aumentar sua confiabilidade. Alguns tipos de uso de apelidos podem ser proibidos pelo projeto de uma linguagem (SEBESTA, 2010). 2.4 Portabilidade Facilidade com a qual os programas podem ser movidos de uma implementação para outra (SEBESTA, 2010). Ou seja, quão facilmente o mesmo código, escrito em uma determinada linguagem de programação, pode ser executado em vários sistemas operacionais e plataformas de hardware. Depende muito do grau de padronização da linguagem, pois quanto mais padronizada for a linguagem mais fácil será mover um programa de uma implementação para outra. Por sua vez a padronização de uma linguagem é um processo difícil e consome muito tempo (SEBESTA, 2010). 2.5 Generalidade Possibilidade de uso da linguagem em uma gama de aplicações. Por exemplo, aplicações desktop, web, mobile, tablets, sistemas embarcados, sistemas distribuídos e outros mais. Um exemplo comum de linguagem com bastante generalidade é o Java. 25 2.6 Compilação A compilaçãoé uma das formas usadas para adequar a uma determinada máquina os códigos de software escritos em uma linguagem diferente da sua (linguagem de máquina). Os programas que podem ser completamente, e de uma vez por todas, traduzidos em linguagens de máquina (isto é, instruções de máquina), onde estas por sua vez possam ser executadas diretamente pelo processador, possuem sua implementação baseada em compilação. As implementações baseadas em compilação contam com a vantagem de ter uma execução de programas muito rápida uma vez que o processo de tradução estiver completo (SEBESTA, 2010). “A maioria das implementações de produção das linguagens, como, C, COBOL, Ada, é feita por meio de compiladores” (SEBESTA, 2010). As principais fazes do processo de compilação são as seguintes: • Análise léxica: agrupa os caracteres do código em identificadores, palavras chaves, operadores e símbolos de pontuação. Essas informações são inseridas em uma Tabela de Símbolos; • Análise sintática: obtém as unidades léxicas e constrói a estrutura hierárquica sintática do programa. Também inserindo e associando as informações conseguidas às informações na Tabela de Símbolos; • Código intermediário e análise semântica: reproduz o programa em uma linguagem de nível intermediário entre o programa-fonte e a saída final do compilador. A análise semântica verifica erros difíceis de serem observados na análise sintática; • Gerador de código: traduz a geração do código intermediário em um programa equivalente à linguagem de máquina; 26 • Tabela de símbolos: serve como uma base de dados para o processo de compilação, esses dados são escritos pelos analisadores léxico e sintático e são lidos pelo analisador semântico e pelo gerador de código. Figura 1 – Processo de compilação. Fonte: SEBESTA, 2010. 27 2.7 Interpretação Pura O método de Interpretação (da linguagem do código fonte para a linguagem de máquina) é outra forma de adequar o código a uma determinada máquina local. Na interpretação pura os programas implementados com uma linguagem de alto nível são traduzidos linha após linha por um outro programa chamado interpretador (onde este já não precisa ser traduzido para funcionar). O interpretador simula uma máquina física que recebe e executa essas linhas (sentenças) de código, porém aqui, ao contrário do que normalmente ocorre em uma máquina real onde as sentenças precisam estar em linguagem de máquina para serem executadas, as sentenças se encontram em alto nível. Essa simulação de máquina física é conhecida como Maquina Virtual (daquela linguagem de alto nível). A interpretação pura tem a vantagem de permitir uma fácil implementação de muitas operações de depuração em código fonte, pois todas as mensagens de erro em tempo de execução podem referenciar unidades de código fonte (SEBESTA, 2010). Porém o método de interpretação pura também traz serias desvantagens como: a lentidão no tempo de execução, que chega a ser entre 10 e 100 vezes mais do que em sistemas compilados; a quantidade de espaço requerido a memória principal, pois tanto a tabela de símbolos como o código fonte tem de ser armazenados na memória; e o fato de todas as vezes em que o programa for usado, se faz necessária uma nova interpretação. Figura 2 – Processo de interpretação. Fonte: SEBESTA, 2010. 28 3 LINGUAGEM SCALA Scala é uma linguagem de programação que integra os recursos de linguagens orientadas a objetos e funcionais (ODERSKY, 2011). Ela foi desenhada para expressar padrões comuns de programação de forma concisa, elegante e tipada (TYPESAFE, 2015). “Permite aos desenvolvedores serem mais produtivos, enquanto retem plena interoperabilidade com Java e aproveitando o hardware multicore moderno” (TYPESAFE, 2015). Muitas organizações conhecidas utilizam (ou já utilizaram) Scala, como por exemplo: Sony, IBM, Twitter, Foursquare, Novell, LinkedIn, Siemens, Xerox, Edf, UBS (ODERSKY, 2011). Scala pode rodar em qualquer máquina com uma JVM (Máquina Virtual Java) instalada (da versão 1.6 às mais recentes), conforme será visto na avaliação da linguagem. Sua distribuição estável mais recente se encontra na versão 2.11.7 e pode ser baixada na página oficial da linguagem Scala (ÉCOLE POLYTECHNIQUE FÉDÉRALE DE LAUSANNE, 2015). 3.1 Histórico Scala foi desenvolvida em 2001 por Martin Odersky (ex-integrante do java Generics) no Laboratório de Métodos de Programação localizado na EPFL (Escola Politécnica Federal de Lausana), Suíça. Sua versão 1.0 foi lançada em novembro de 2003 (ÉCOLE POLYTECHNIQUE FÉDÉRALE DE LAUSANNE, 2015; FURTADO e PERIN e HEBEDA, 2012). Foi liberada publicamente na plataforma Java em janeiro de 2004, e na plataforma .NET em junho do mesmo ano. Tendo uma segunda versão da linguagem liberada em março de 2006 (FURTADO e PERIN e HEBEDA, 2012). Na verdade, Scala tem sido um esforço coletivo de muitas pessoas. O design e a implementação da versão 1.0 foi concluída por Philippe Altherr, Vincent Cremet, Gilles Dubochet, Burak Emir, Stéphane Micheloud, Nikolay Mihaylov, Michel Schinz, Erik 29 Stenman, Matthias Zenger, e, o autor, Martin Odersky (ÉCOLE POLYTECHNIQUE FÉDÉRALE DE LAUSANNE, 2015). Iulian Dragos, Gilles Dubochet, Philipp Haller, Sean McDirmid, Lex Spoon, e Geoffrey Washburn se juntaram ao esforço para desenvolver a segunda versão da linguagem e ferramentas. Gilad Bracha, Craig Chambers, Erik Ernst, Matthias Felleisen, Shriram Krishnamurti, Gary Leavens, Sebastian Maneth, Erik Meijer, Klaus Ostermann, Didier Rémy, Mads Torgersen, e Philip Wadler moldaram o projeto da linguagem através de discussões e comentários animados e inspiradores… (ÉCOLE POLYTECHNIQUE FÉDÉRALE DE LAUSANNE, 2015). Os contribuintes para a lista de discussão Scala também deram um feedback útil que ajudou a melhorar a linguagem e suas ferramentas (ÉCOLE POLYTECHNIQUE FÉDÉRALE DE LAUSANNE, 2015). Em 17 de Janeiro de 2011 o Time Scala ganhou uma bolsa de pesquisa de 5 anos no valor de 2,3 milhões de Euros do European Research Council (Conselho Europeu de Pesquisa). E em maio do mesmo ano, Odersky e alguns colaboradores fundaram a Typesafe, uma empresa para dar suporte comercial, treinamento e serviços para Scala (FURTADO e PERIN e HEBEDA, 2012). 3.2 Características técnicas Scala é uma linguagem orientada a objeto e funcional, compatível com Java (ODERSKY, 2006). Um componente Scala pode acessar todos os métodos e campos de um componente Java, pode criar instâncias de classes Java, pode herdar de classes Java, implementar interfaces Java, e pode ser instanciado e chamado a partir de um componente Java (ODERSKY, 2006). Observe um exemplo de interoperabilidade entre Java e Scala implementado na figura 3. Percebe-se que as declarações de importação de Scala são semelhantes às equivalentes em Java. Em Scala várias classes podem ser importadas do mesmo pacote ao envolvê-las com colchetes, por exemplo a instrução import java.util.{Date, Locale}. Quando se está importando tudo dentro de um pacote ou classe específica Scala utiliza o caractere underline “_” em vez de asteriscos “*”, isto pode ser visto na declaração de 30 importação import java.text.DateFormat._, que torna o método getDateInstace e o campo LONG (ambos estáticos) diretamente disponíveis na implementação. Dentro do método main é criada uma instância da classe Date (pertencente ao Java), val agora = new Date, que por padrão contém a data atual. No passo seguinte é escolhidoum formato de data com o método estático getDateInstance, neste caso o formato de data escolhido foi o utilizado na França. E, a declaração println(formatoData.format(agora)) imprime a data armazenada em agora com o formato de data escolhido. Figura 3 – Importação com Scala. Fonte: SCHINZ; HALLER, 2015. 3.3 Operadores Um operador é um símbolo que fala ao compilador para efetuar manipulações matemáticas ou lógicas específicas (TUTORIALSPOINT, 2015). Scala é rica em operadores internos e fornece os seguintes tipos de operadores: 31 3.3.1 Operadores Aritméticos Supondo que variável A =10 e variável B = 20, observe na tabela 1 a apresentação dos operadores aritméticos da linguagem Scala e seu emprego com as variáveis A e B. Tabela 1 – Operadores aritméticos. OPERADOR DESCRIÇÃO EXEMPLO + Adiciona dois operandos A + B dará 30 - Subtrai o segundo operando do primeiro A – B dará -10 * Multiplica os dois operandos A * B dará 200 / Divide dividendo pelo divisor B / A dará 2 % Operador modular e resto de uma divisão de inteiros B % A dará 0 Fonte: TUTORIALSPOINT, 2014. 3.3.2 Operadores Relacionais Supondo que variável A =10 e variável B = 20, observe a tabela 2 a apresentação dos operadores relacionais da linguagem Scala e seu emprego com as variáveis A e B. Tabela 2 – Operadores relacionais. OPERADOR DESCRIÇÃO EXEMPLO == Checa se os valores dos dois operandos são iguais A == B não é verdadeiro != Checa se os valores de dois operando não são iguais A != B é verdadeiro > Checa se o valor do operando a esquerda é maior que o da direita A > B não é verdadeiro < Checa se o valor do operando a esquerda é menor que o da direita A < B é verdadeiro 32 >= Checa se o valor do operando a esquerda é maior ou igual ao da direita A >= B não é verdadeiro <= Checa se o valor a esquerda é menor ou igual ao da direita A <= B é verdadeiro Fonte: TUTORIALSPOINT, 2014. 3.3.3 Operadores Lógicos Supondo que variável A =1 e variável B = 0, observe a tabela 3 a apresentação dos operadores lógicos da linguagem Scala e seu emprego com as variáveis A e B. Tabela 3 – Operadores lógicos. OPERADOR DESCRIÇÃO EXEMPLO && Operador lógico and. (A && B) é falso || Operador lógico ou. (A || B) é verdadeiro ! Operador lógico not. !(A && B) é verdadeiro Fonte: TUTORIALSPOINT, 2014. 3.3.4 Operadores Bit a Bit Operadores bit a bit funcionam em bits e executam operações bit a bit. Propondo que A = 60 e B = 13, observe na tabela 4 operações binárias com as variáveis A e B. Tabela 4 – Operadores bit a bit. OPERADOR DESCRIÇÃO EXEMPLO & Operador binário and que multiplica os bits correspondentes entre A e B (A & B) dará 0000 1100 que é o mesmo que 12 em formato inteiro. | Operador binário or que cópia o maior bit correspondente onde os operando não forem (A | B) dará 0011 1101 que é o mesmo que 61 em formato inteiro. 33 ambos nulos ^ Operador binário xor que atribui 1 onde os operando não forem iguais. (A ^ B) dará 0011 0001 que é o mesmo que 49 em formato inteiro. ~ Operador binário unário de negação. (~A) dará 1100 0011 que é o mesmo que -61 << Operador binário left shift que de acordo com o valor especificado “move” os bits da direita para esquerda, preenchendo com 0 (zero) as posições agora abertas A << 2 dará 1111 0000 que é o mesmo que 240 em formato inteiro >> Operador binário right shift que de acordo com o valor especificado “move” os bits da esquerda para direita. A >> 2 dará 1111 que equivale a 15 em formato inteiro. >>> Operador binário shift right zero que de acordo com o valor especificado “move” os bits da esquerda para direita, preenchendo com 0 (zero) as posições agora abertas. A >>> 2 dará 0000 1111 que equivale também a 15 em formato inteiro. Fonte: TUTORIALSPOINT, 2014. 3.3.5 Operadores de Atribuição Operadores de atribuição e suas equivalências são demonstrados na tabela 5. Tabela 5 – Operadores de atribuição. OPERADOR DESCRIÇÃO EXEMPLO = Operador de atribuição simples. Atribui valores de C = A + B atribuirá o valor resultado de A + B para C. 34 operandos do lado direito para o operando no lado esquerdo do operador. += Operador para adicionar e atribuir. Ele soma o operando direito e operando esquerdo ao operador, atribuindo o resultado ao operando esquerdo. C += A é equivalente a C = C + A. -= Operador para subtrair e atribuir. Ele subtrai o operando direito do operando esquerdo ao operador, atribuindo o resultado ao operando esquerdo. C -= A é equivalente a C = C – A. *= Operador para multiplicar e atribuir. Ele multiplica o operando direito com o operando esquerdo ao operador, atribuindo o resultado ao operando esquerdo. C *= A é equivalente a C = C * A. /= Operador para dividir e atribuir. Ele divide o operando esquerdo pelo operando direito ao operador, atribuindo o resultado ao operando esquerdo. C /= A é equivalente a C = C / A. %= Operador para modular e atribuir. Ele captura o resto da divisão entre os operandos esquerdo e direito do C %= A é equivalente a C = C % A. 35 operador, atribuindo o resultado ao esquerdo. <<= Left shift e operador de atribuição. C <<= 2 é o mesmo que C = C << 2. >>= Right shift e operador de atribuição. C >>= 2 é o mesmo que C = C >> 2. &= AND Bit a bit e operador de atribuição. C &= 2 é o mesmo que C = C & 2. ^= XOR bit a bit e operador de atribuição. C ^= 2 é o mesmo que C = C ^ 2. |= OR bit a bit inclusivo e operador de atribuição; C |= 2 é o mesmo que C = C | 2. Fonte: TUTORIALSPOINT, 2014. 3.3.6 Precedência de Operadores A precedência de operadores determinará grupos de termos em uma mesma expressão. Isto afeta o modo como as expressões são validadas, pois alguns operadores terão maior precedência que outros. Por exemplo, tomemos a expressão x = 7 + 3 * 2; aqui a x é atribuído o valor 13 e não 20, pois o operador “*” tem maior precedência que “+”, então o primeiro a ser calculado aqui é 3 * 2 e somente então soma o valor com 7. Na tabela 6 a seguir, os operadores com maior precedência aparecem nas primeiras posições. Tabela 6 – Precedência de operadores. CATEGORIA OPERADOR ASSOCIATIVIDADE Postfix ( ) [ ] Esquerda para direita Unário ! ~ Direita para esquerda Multiplicativo * / % Esquerda para direita Aditivo + - Esquerda para direita Shift (deslocação) >> >>> << Esquerda para direita Relacional > >= < <= Esquerda para direita Igualdade == != Esquerda para direita 36 AND bit a bit & Esquerda para direita XOR bit a bit ^ Esquerda para direita OR bit a bit | Esquerda para direita AND lógico && Esquerda para direita OR lógico || Esquerda para direita Atribuição = += -= *= /= %= >>= <<= &= ^= Direita para esquerda Virgula , Esquerda para direita Fonte: TUTORIALSPOINT, 2014. 3.4 Ambiente de Testes Os testes para avaliação da linguagem Scala na versão 2.11.5 deram-se em computador particular usando o sistema operacional Windows 7 Professional Edition (MICROSOFT, 2015), mas Scala é compatível com qualquer plataforma que possua uma JVM entre a versão 1.6 e alguma versão 8 instalada. O ambiente escolhido para as demonstrações foi o Diretório de Gerenciamento de Comandos (CMD) do Windows junto com um interpretador Scala chamado REPL (Read – Eval – Print – Loop, isto é,Leia – Valide – Imprima – Faça de novo), um recurso muito interessante para os iniciantes se familiarizarem com a sintaxe da linguagem e mesmo para os veteranos executarem testes de APIs. APIs são conjuntos de rotinas e padrões estabelecidos por um software para a utilização das suas funcionalidades por aplicativos que não pretendem envolver-se em detalhes da implementação do software, mas apenas usar seus serviços. Em alguns momentos será necessário a utilização da ScalaIDE para Eclipse (TYPESAFE, 2015) para a realização de demonstrações mais complexas e completas. Por fim, após as devidas configurações para criar uma variável de ambiente Scala, pode-se executar o REPL pelo terminal digitando “scala” conforme mostra a figura 4. 37 Figura 4 – Interpretador REPL. Fonte: Autor. Na figura 5 vê-se um erro de interpretação devido ao comando println (imprimir informação na tela) ter sido digitado com a sintaxe incorreta “printlm”. Figura 5 – Erro de interpretação. Fonte: Autor. Em Scala um ponto e vírgula “;” sempre indica o fim de uma expressão, porém, não é obrigatório para isto. Ao mesmo tempo, e a depender da situação em que for usada, uma nova linha (Enter) também pode indicar o fim de uma expressão. Tem-se como exemplo o que acontece na figura 6. Figura 6 – Inferência do “;”. Fonte: Autor. 38 Mas para que o código aqui possa funcionar da maneira esperada as seguintes condições precisam ser atendidas: 1. A linha em questão NÃO deve terminar com alguma palavra (como um período ou um operador infixo) que não seja o legal (correto, permitido) para o fim de uma expressão. Exemplo: def Exemplo_2(x: Int) = x * 2. A próxima linha NÃO deve começar com uma palavra incorreta para o início de uma nova expressão. Exemplo: def Exemplo_2(x: Int) = (x + y) 3. Código tem de estar fora de parênteses “()” ou colchetes “[]”, caso contrário precisará utilizar ponto e vírgula “;”. Vale mencionar também que Scala é uma linguagem case sensitive, ou seja, caixas maiúsculas e minúsculas são tratadas de forma diferente pela linguagem, por exemplo, Exemplo_1 é algo diferente de exemplo_1. 3.5 Expressões e Funções Simples Observe alguns exemplos de expressões simples na figura 7. Não há nenhuma atribuição de valor aqui, mas em situações assim o interpretador REPL automaticamente cria values (espaços de memória alocados que funcionam como constantes), por exemplo, res0, res1, res2, etc … para alocar o resultado de cálculos e expressões. 39 Figura 7 – Expressões simples. Fonte: Autor. Scala possui inferência de tipos, ou seja, não é obrigatório sempre declarar o tipo de dados, ainda que isso seja recomendado para uma boa documentação, mas de fato Scala foi desenvolvida para em muitas situações descobrir o tipo de dados que está sendo atribuído, Isto vale para values, variáveis e funções. Caso necessite, o desenvolvedor pode usar as values criadas pelo interpretador, conforme mostra a figura 8. Figura 8 – Values criados pelo REPL. Fonte: Autor. Funções em Scala são chamadas de Definições. Definições são semelhantes a métodos em Java, e são declaradas usando a palavra reservada def, que deve ser seguida respectivamente do nome da função, seus parâmetros (se houverem), seu tipo de retorno (optativo por causa da inferência de tipos do Scala), e por fim, seu corpo após o sinal “=”, tal como é apresentada no quadro 6. 40 Quadro 6 – Definições em Scala. def nomeFuncao(parametro: Tipo de dado): Tipo de dado = expressão Fonte: Autor. Scala oferece a possibilidade de criação de values, como mostra a value vCubo na figura 9. Values são indicadas pela palavra reservada val. Figura 9 – Validação de values. Fonte: Autor. Uma função (def) é validada substituindo no código o seu nome pelo seu corpo não resolvido, isto é não reduzido a algum valor (tal como ele está escrito em sua definição). Alternativamente, uma value (val) é validada substituindo o seu nome no código pelo seu corpo já resolvido, isto é, já reduzido a um resultado. Quando então a value for utilizada no código imediatamente ela será substituída pelo seu valor já pré-computado, não precisando ser validada novamente. A função, por sua vez, precisará sempre ser resolvida (reduzida) todas as vezes que for utilizada pelo código. 3.6 Valores Imutáveis As values, apresentadas no tópico anterior, são consideradas valores imutáveis, semelhantes a constantes Java definidas pela palavra reservada final, como por exemplo: final String nome = “Adriel”. Observe no exemplo da figura seguinte que ao ser atribuído outro valor a value nome ocorre um erro no interpretador. 41 Figura 10 – Valores imutáveis. Fonte: Autor. Portanto, ao declarar-se um valor imutável (val), é preciso que já em sua declaração lhe seja atribuído algum valor, pois não será permitido alterar ou atribuir mais nada a val durante o resto do código. Observa-se no código o método principal (main) e o tipo desse método (Unit, logo a direita dos parâmetros). Unit é semelhante ao void do Java, e é ele quem indica que o método principal não retorna nada. Scala também trabalha com valores mutáveis, ou seja, variáveis, através da palavra reservada var (semelhantes a atributos em outras linguagens de programação). 3.7 Parâmetros Sobre toda função parametrizada o compilador/interpretador do Scala possui duas formas diferentes de chamar argumentos recebidos ao corpo da função: call-by-value e call- by-name. Caso nada seja especificado pelo desenvolvedor, a forma call-by-value é adotada como padrão pelo compilador/interpretador, e exatamente isto (call-by-value adotada como padrão) acontece no código da figura 11, onde as funções quadrado e somaDeQuadrados são criadas de forma normal. 42 Figura 11 – Validação call-by-value. Fonte: Autor. Observe no quadro 7 como a função somaDeQuadrados(3, 5+7) da figura 11 foi validada pelo interpretador. A passagem de parâmetros por call-by-value segue os seguintes passos: 1. Cada argumento passado a função, da esquerda para a direita, é resolvido (reduzido a um resultado) por meio de um modelo de substituição de sentenças. 2. Feito isto, a chamada da função (no código) é substituída pelo seu corpo já com os argumentos resolvidos. Quadro 7 – Chamada de argumentos call-by-value. SomaDeQuadrados(3, 5+7) //Argumento ainda não foi resolvido somaDeQuadrados(3, 12) //Argumento resolvido quadrado(3) + quadrado(12) //Corpo com argumentos resolvidos 3 * 3 + quadrado(12) 9 + quadrado(12) 9 + 12 * 12 9 + 144 153 Fonte: Autor. Porém há situações em que o interpretador não consegue validar uma expressão em um número finito de passos com call-by-value. Por exemplo, observe a função teste no quadro 8 e note que o argumento loop está sendo passado a função por meio de teste(1, loop). Contudo loop não é utilizado em nada no corpo de teste. E ali, justamente pelo 43 interpretador querer resolver loop, será impedido de chegar a um resultado em um número finito de passos. Tem-se então a função teste2 utilizando o modelo de validação call-by-name, que possibilita aplicar ao corpo da função os argumentos ainda não reduzidos, e, somente os que ele utilizará (os necessários). Se o tipo do parâmetro de uma função for escrito começando com o símbolo =>, Scala usará call-by-name para validar aquele parâmetro. Quadro 8 – Função loop. def loop: Int = loop //Validação com call-by-value def teste(x: Int, y: Int): Int = x teste(1, loop) //Validação com call-by-name def teste2(x: Int, y: => Int): Int = x teste2(1, loop) Fonte: Autor. Observe como o exemplo da figura 11 é reescrito na figura 12 com a utilizaçãode call-by-name em dado momento. Figura 12 – Validação call-by-name. Fonte: Autor. Ambas estratégias convergem ao mesmo resultado, diferenciando apenas o número de passos dados. Observe no quadro 9 a função somaDeQuadrados(3, 5+7) sendo validada, agora com call-by-name. 44 Quadro 9 – Chamada de argumentos call-by-name. somaDeQuadrados(3, 5+7) quadrado(3) + quadrado(5+7) 3 * 3 + quadrado(5+7) 9 + quadrado(5+7) 9 + (5+7) * (5+7) 9 + 12 * (5+7) 9 + 12 * 12 9 + 144 153 Fonte: Autor. Call-by-value possui a vantagem de evitar repetidas resoluções dos argumentos. Já Call-by-name possui a vantagem de evitar resolver argumentos nos casos em que um determinado parâmetro não está sendo usado em nada pelo corpo da função (quadro 8). Call- by-value é usualmente mais eficiente que call-by-name, mas, call-by-value pode entrar em loop onde call-by-name poderia concluir seu dever. 3.8 Expressão Condicional If-else O If-else do Scala permite uma escolha entre duas expressões alternativas. Sua sintaxe é como o if-else do Java. Mas onde if-else do Java é utilizado apenas como uma alternativa de declarações simples, Scala pode usar a mesma sintaxe para escolher entre duas expressões. Observe o exemplo de If-else em Scala na figura 13. Figura 13 – Exemplo If-else em Scala. Fonte: Autor. Expressões If do Scala também podem resultar em um valor, semelhantemente ao operador ternário do java (ODERSKY e SPOON e VENNERS, 2008). 45 3.9 Recursão de Cauda (Tail Recursion) Funções recursivas são definições que em algum lugar do seu corpo chamam a si mesmas. Observe a função da figura 14 (fatoracao) e sua validação no quadro 10. Na figura 14, pelo modo como a recursão é ali implementada obtêm-se cadeias mais longas e mais longas de operandos, sendo necessário alocar cada vez mais espaço de memória, até que enfim, os operandos sejam multiplicados na última parte da sequência de validação 5 * (4 * (3 * (2* (1 * 1)))). Figura 14 – Recursividade comum. Fonte: Autor. Quadro 10 – Validação da fatoração. fatoracao(5) if(5 == 0) 1 else 5 * fatoracao(5 – 1) 5 * fatoracao(5 – 1) 5 * fatoracao(4) … 5 * (4 * fatoracao(3)) … 5 * (4 * (3 * fatoracao(2))) … 5 * (4 * (3 * (2* fatoracao(1)))) … 5 * (4 * (3 * (2* (1 * fatoracao(0))))) … 5 * (4 * (3 * (2* (1 * 1)))) … 120 Fonte: Autor. Scala possibilita o uso de recursões de cauda (Tail Recursion) que é uma forma mais dinâmica e interativa de serem feitas recursões, possibilitando que elas sejam executadas em um espaço de memória constante. 46 Veja na figura 15 a mesma função, agora, fazendo uso de recursão de cauda. A recursão fatoracao(n-1, result*n) está sendo a última ação realizada no if-else, e sem mais nenhuma operação ligada a ela. O fato de não ter mais nenhuma operação ligada a ela, por exemplo, … n * fatoracao, faz com que nenhum novo espaço de pilha, isto é de memória, seja necessário, e aqui os sucessivos argumentos gerados pela recursão fatoracao(n-1, result*n) apenas ficarão substituindo os atuais. Por isso o nome “recursão de cauda”, pois não deixa um rastro de valores armazenados para realizar o calculo somente no último momento. Por fim, em Scala toda função recursiva (de cauda ou não) precisa declarar explicitamente seu tipo de retorno, por exemplo, o tipo de retorno da função fatoracao é Int. Figura 15 – Recursão de cauda. Fonte: Autor. Observe a forma como a função com recursão de cauda é validada. Quadro 11. Quadro 11 – Validação com recursão de cauda. fatoracao(5, 1) if(5 == 1) 1 else fatoracao(5–1, 1*5) if(4 == 1) 5 else fatoracao(4–1, 5*4) if(3 == 1) 20 else fatoracao(3–1, 20*3) if(2 == 1) 60 else fatoracao(2–1, 60*2) if(1 == 1) 120 120 Fonte: Autor. 47 Em princípio, as recursões de cauda sempre podem reusar o quadro de pilha (reusar o mesmo espaço de memória) da função que a chama. No entanto, alguns ambientes run-time (como o JVM 7) não têm as primitivas para fazer reúso eficiente do quadro de pilha para recursões de cauda. Uma implementação Scala feita com qualidade é, portanto, requerida para reutilizar o quadro de pilha de uma função recursiva de cauda. Não se deve contar com esse recurso em toda implementação. 3.10 Funções de Ordem Superior (High-Order Functions) Uma função em Scala é considerada uma espécie de “valor de primeira classe”. Como qualquer outro valor, funções podem ser passadas como argumentos ou devolvidas como resultado. Funções cujos parâmetros recebam outras funções como argumentos, e/ou, devolvam funções como resultado, são chamadas de Funções de Ordem Superior (ODERSKY, 2014). Elas fornecem um mecanismo flexível para a composição de programas e são um traço forte na linguagem Scala. Observe os exemplos dos quadros 12 e 13 seguintes. Neles foram implementadas funções que calculam a soma de valores inteiros, cubos e fatoriais dentro de algum intervalo. O quadro 12 faz isto se utilizando de funções comuns enquanto o quadro 13 se utiliza de funções de ordem superior. 48 Quadro 12 – Funções comuns. //SOMA DE INTEIROS def somaInts(a: Int, b: Int): Int = if (a > b) 0 else a + somaInts(a + 1, b) //SOMA DE CUBOS def cubo(x: Int): Int = x * x * x def somaCubos(a: Int, b: Int): Int = if (a > b) 0 else cubo(a) + somaCubos(a + 1, b) //SOMA DE FATORIAIS def fatorial(n: Int, result: Int): Int = if (n == 0) result else fatorial(n-1, result*n) def somaFatoriais(a: Int, b: Int): Int = if (a > b) 0 else fatorial(a, 1) + somaFatoriais(a + 1, b) Fonte: Autor. Uma observação importante para a compreensão do quadro 13 seguinte: o parâmetro f: Int => Int da função soma indica que para aquele parâmetro o argumento precisa ser uma função, Int => Int indica que esta função pode ser qualquer uma que receba um inteiro como argumento e retorne um inteiro como resultado. As funções somaInts, somaCubos, e somaFatoriais estão retornando como resultado as funções soma(id, a, b), soma(cubo, a, b) e soma(fatorial, a, b). Quadro 13 – Funções de ordem superior. def id(x: Int): Int = x def cubo(x: Int): Int = x * x * x def fatorial(x: Int): Int = if (x == 0) 1 else x * fatorial(x - 1) def soma(f: Int => Int, a: Int, b: Int): Int = if (a > b) 0 else f(a) + soma(f, a + 1, b) def somaInts(a: Int, b: Int) = soma(id, a, b) def somaCubos(a: Int, b: Int) = soma(cubo, a, b) def somaFatoriais(a: Int, b: Int) = soma(fatorial, a, b) Fonte: Autor. 49 Ao comparar o quadro 12 com o quadro 13 destacam-se duas diferenças entre as duas implementações do mesmo problema. 1. Com o uso de funções de ordem superior a dependência que as funções têm uma das outras é maior. 2. O número de funções no quadro 13 para resolver o problema foi maior, contudo, no geral as funções ficaram mais simples do que as implementadas no quadro 12. As funções anônimas (tópico seguinte) tem influência positiva sobre os dois aspectos citados acima. 3.11 Funções Anônimas O uso de funções como parâmetros tende a criar muitas funções pequenas (ODERSKY, 2014). Ao invés do desenvolvedor criar várias e pequenas funções com suas assinaturas (nome da função, parâmetros e tipo de retorno) para servirem de argumentos a funções de ordem superior, Scala lhe possibilita formulá-las de forma mais rápida e curta com o uso de Funções Anônimas (Anonymous Function). Uma função anônima é uma função que é definida sem dar-lhe um nome, ao mesmo tempo que o compilador/interpretador a trata da mesma forma que uma função qualquer. Como exemplo, na figura 16, considere a função anônima logo abaixo da função cubo, esta função anônima realiza o mesmo que a função cubo. Em funções anônimas a parte
Compartilhar