Logotipo

Descrição gerada automaticamente

 

LANG

 

Autores: Elian de Oliveira e Rodrigo Pansolim da Rosa

 

DEFINIÇÃO

 

D é uma linguagem de programação de sistemas que se baseia muito na linguagem C. Ela preenche lacunas que aparecem em algumas linguagens em que seria necessário buscar auxílio de outros programas externos para preenchê-las.

Ao programar usando D podem ser vistos aspectos da linguagem Python, Java e C++, dando a impressão de desenvolver em uma mistura de linguagens, que de maneira geral é o que acontece. Conceitos como orientação a objetos, paralelismo e distribuição podem ser feitos em D.

Também é uma linguagem de alto nível, que reúne o útil com o agradável tornando-o capaz de produzir programas robustos sendo simples de aprender, com suporte suficiente para os desenvolvedores, além de trazer conceitos atuais que linguagens antigas não trazem com elas, pois o poder de computação deve ser equivalente ao nível de softwares atuais para se obter desempenhos elevados.

 

TRANSIÇÃO PARA D

 

A transição de C/C++ D deve parecer natural, uma vez que a semântica é muito parecida, com o visual geral de D sendo como em C e C++, assim programadores não precisam começar a aprender do zero, precisando aprender a princípio as diferenças na sintaxe e como os novos métodos interagem no desenvolvimento dos códigos.

Já no decorrer do código não é necessário desenvolver apenas em programação sequencial, a orientação a objetos também faz parte da linguagem e D possibilita utilizar ambas no desenvolvimento, além disso, pode-se fazer o tratamento de exceções para controlar erros e avisos no assim código como em Java.

 

O uso de D não significa que o programador ficará restrito a uma MV (virtual machine) especializada em tempo de execução, como o Java. Não há MV em D, é um compilador simples que gera arquivos de objetos vinculáveis. D se comunica com o sistema operacional como C. As ferramentas habituais familiares como makefile se encaixarão perfeitamente com o desenvolvimento D.

 

·      A aparência geral de C/C++ é adotada. Ele usa a mesma sintaxe algébrica, a maioria das mesmas formas de expressão e declaração, e o layout geral.

·      Os programas D podem ser escritos em paradigmas familiares, função e dados, orientação a objetos, modelos de metaprogramação, funcional, ou qualquer mistura deles.

·      O modelo de desenvolvimento de compilação/link/ depuração é levado adiante a partir de linguagens como C, embora nada impeça que D seja compilado e interpretado em bytecode.

·      Uma maior experiência com tratamento de exceções se mostra uma maneira superior de lidar com erros do que códigos de erro.

·      D mantém compatibilidade de link de função com as convenções de chamada C. Isso possibilita que os programas D acessem diretamente as APIs do sistema operacional. O conhecimento e a experiência dos programadores com APIs e paradigmas de programação existentes podem ser levados em consideração.

·      Os programas D podem sobrecarregar os operadores que permitem a extensão dos tipos básicos com tipos definidos pelo usuário.

·      O polimorfismo paramétrico (também conhecido como metaprogramação) é simples em D.

·      Gerenciamento personalizado de memória. Há momentos na programação de sistemas em que os desenvolvedores precisam de alternativas para a coleta de lixo. D também permite o gerenciamento manual da memória, técnicas como contagem de referências e o uso de alocadores de memória especializados.

Entre outros aspectos de transição.

 

SINTAXES

 

Algumas das diferenças que a linguagem D tem não preocupam programadores que já experimentaram linguagens mais populares, por essa razão a sintaxe que a linguagem D possui é muito semelhante.

Alguns aspectos básicos que se destacam são os mostrados a seguir e há outros que, no decorrer da utilização da linguagem, são associados naturalmente, pois a lógica utilizada é a mesma para todas as linguagens. Essas são partes básicas que a linguagem D aborda e para um maior aprofundamento pode ser visto no próprio site da Dlang (https://dlang.org).

 

·      import: inclui uma biblioteca, como em Python ou #include em C;

·      immutable: define as constantes para o programa não sendo necessário definir o tipo, como #define em C;

·      foreach: um laço de repetição adicional para abranger mais soluções que foi desenvolvido com a linguagem D;

·      unsigned: para declarar variáveis só positivas é necessário adicionar a letra u em frente do tipo do dado, no caso de um unsigned int em C ficaria como uint em D;

·      writeln: é muito parecido como o System.out.println em Java que consegue alternar entre texto e variaveis no decorrer da função.

 

PROGRAMAÇÃO ORIENTADA A OBJETOS

 

Classes

As classes são quem dão à linguagem D suporte à orientação a objetos, aspectos como encapsulamento, herança e polimorfismo estão presentes também e, de maneira muito similar a estruturas, a maneira de criação e acesso são parecidas, mas há diferenças notáveis quanto ao utilizar classes. A maior diferença entre ambas é que estruturas trabalham como tipos de valores e classes são como referências, outras diferenças entre elas são principalmente devido a esse fato.

O manuseamento das classes em D é similar à Java, conceitos de criações de novas classes, comparações, construtores e acessos são facilmente compreendidos, mostrando como D engloba aspectos importantes de outras linguagens.

Um exemplo da diferença de declarações de classes e estrutura pode ser visto abaixo:

import std.stdio;

 

struct MinhaEstrutura {

  int dado;

}

class MinhaClasse {

  int dado;

}

void main() {

  //interação com estrutura

  MinhaEstrutura e1;

  MinhaEstrutura e2 = e1;

  ++e2.dado;

  writeln(e1.dado); // mostraria 0

  //interação com classe

  MinhaClasse c1 = new MinhaClasse;

  MinhaClasse c2 = c1;

  ++c2.dado;

  writeln(c1.dado); // mostraria 1

}

 

Sobrecarga do operador

A sobrecarga (overload) consiste em permitir, dentro da mesma classe, mais de um método com o mesmo nome.

Podem ser elaboradas classes que trabalham com operadores existentes para ampliar o sistema de tipos para suportar novos tipos. Um exemplo seria criar uma classe de grande número e, em seguida, sobrecarregar os +, -, * e / operadores para permitir o uso de sintaxe algébrica comum com eles.

A sobrecarga do operador é realizada reescrevendo operadores cujos operandos são objetos de classe ou estrutura em chamadas para membros especialmente nomeados. Não é usada sintaxe adicional.

Por exemplo, a fim de sobrecarregar o operador - (negação) para a estrutura S, e nenhum outro operador:

struct S

{

    int m;

 

    int opUnary(string s)() if (s == "-")

    {

        return -m;

    }

}

 

int foo(S s)

{

    return -s;

}

 

PROGRAMAÇÃO FUNCIONAL

 

A programação funcional tem muito a oferecer em termos de encapsulamento, programação simultânea, segurança de memória e composição. O suporte de D para programação de estilo funcional inclui:

 

·      Funções puras

·      Tipos imutáveis e estruturas de dados

·      Funções e fechamentos da Lambda

 

PRODUTIVIDADE

 

Módulos

Os arquivos de origem têm uma correspondência um-para-um com módulos.

 

Declaração vs Definição

Funções e classes são definidas uma vez. Não há necessidade de declarações quando forem encaminhadas. Um módulo pode ser importado, e todas as suas declarações públicas ficam disponíveis para o importador.

 

Exemplo:

class ABC

{

    int func() { return 7; }

    static int z = 7;

}

int q;

 

Todos os membros são definidos na classe ou na estrutura, não separadamente.

class Foo

{

    int foo(Bar c) { return c.bar; }

}

 

class Bar

{

    int bar() { return 3; }

}

Se uma função em D está ou não embutida é determinado pelas configurações do otimizador.

 

Templates

Os templates em D oferecem uma maneira limpa de suportar a programação genérica, oferecendo o poder da especialização parcial. Os templates de classes e os templates de funções estão disponíveis, juntamente com templates de Arrays (Vetores) variados e tuplas.

 

Matrizes Associativas

Matrizes associativas são matrizes com um tipo de dados arbitrário como o índice, em vez de se limitar a um índice inteiro. Em essência, matrizes associadas são tabelas hash. As matrizes associativas facilitam a construção de tabelas de símbolos rápidas, eficientes e livres de bugs.

 

BIBLIOTECA PHOBOS RUNTIME

 

Phobos é a biblioteca padrão de tempo de execução que vem com o compilador de idiomas D.

Geralmente, o namespace std é usado para os módulos principais na biblioteca padrão Phobos. O namespace etc é usado para ligações externas da biblioteca C/C++. O namespace core é usado para funções de tempo de execução de baixo nível em D.

A biblioteca inclui os seguintes módulos: Algoritmos & ranges, Manipulação de matrizes, Containers, Formatos de dados, Integridade dos dados, Data e hora, Tratamento de exceção, Ligações externas da biblioteca, Sistema I/O e arquivo, Interoperabilidade, Gerenciamento de memória, Polimorfismo, Multitarefa, Rede, Numérico, Manipulações de tipo, Programação vetorial, entre outros.

 

FUNÇÕES

 

D tem o suporte esperado para funções comuns, incluindo funções globais, funções sobrecarregadas, funções embutidas, funções de membro, funções virtuais, ponteiros de função, entre outras. Além disso:

 

Funções Aninhadas

Funções podem ser aninhadas dentro de outras funções. Isso é altamente útil para técnicas de fatoração, localidade e fechamento de funções.

 

Funções Literais

Funções anônimas podem ser incorporadas diretamente em uma expressão.

 

Fechamentos dinâmicos

Funções aninhadas e funções de membro da classe podem ser referenciadas com fechamentos (também chamados de delegados), tornando a programação genérica muito mais fácil e de tipo segura.

 

Parâmetros In, Out e Ref

Não só especifica essa ajuda torna as funções mais auto documentadas, como elimina grande parte da necessidade de ponteiros sem sacrificar nada, e abre possibilidades para mais ajuda do compilador na busca de problemas de codificação.

Isso torna possível a comunicação de D diretamente com uma interface e, desta, com uma variedade mais ampla de APIs estrangeiras. Não haveria necessidade de soluções alternativas como "Linguagens de Definição de Interface".

 

VETORES E MATRIZES

 

Ambos os conceitos de vetores e matrizes em linguagem C possuem diferenças quando levadas para o ambiente D, alguns aspectos são alterados para uma melhor utilização na linguagem e outros são arrumados e adicionados para completar lacunas presentes.

As matrizes C têm várias falhas que podem ser corrigidas:

·      As informações das dimensões não são transportadas com a matriz, e por isso devem ser armazenadas e passadas separadamente. O exemplo clássico disso são os parâmetros argc e argv para main(int argc, char *argv[]). (Em D, o main é declarado como main(string[] args).)

·      Vetores não são objetos de primeira classe. Quando um vetor é passado para uma função, é convertida em um ponteiro, mesmo que o protótipo confusamente diga que é uma matriz. Quando essa conversão acontece, todas as informações do tipo de vetor são perdidas.

·      Vetores em C não podem ser redimensionados. Isso significa que mesmo agregados simples como uma pilha precisam ser construídos como uma classe complexa.

·      Vetores em C não podem ser verificados, porque não sabem quais são os limites do vetor ou matriz.

·      Os vetores são declarados com o [] após o identificador. Isso leva a uma sintaxe muito desajeitada para declarar coisas como um ponteiro para uma matriz:

int (*array)[3];

Em D, o [] para a matriz vai à esquerda:

int[3]* array;      // declares a pointer to an array of 3 ints

long[] func(int x); // declares a function returning an array of longs

O que é muito mais simples de entender.

As matrizes D vêm em várias variedades: ponteiros, matrizes estáticas, matrizes dinâmicas e matrizes associativas.

 

STRINGS

 

A manipulação de strings precisa de suporte direto na linguagem. As linguagens modernas lidam com concatenação de strings, copias, entre outros, assim como as strings na linguagem D. Strings são uma consequência direta do melhor manuseio de vetores.

 

RANGES(INTERVALOS)

 

D usa o conceito de uma range em vez de iteradores ou geradores encontrados em outras línguas. Um range é qualquer tipo que fornece uma interface comum a uma sequência de valores. O objetivo de um range é permitir uma maneira mais simples de escrever códigos que funcionem em dados arbitrários, tornando-o reutilizável.

 

O tipo mais básico de range é chamado de input range(intervalo de entrada), que fornece três métodos.

struct MyRange

{

    auto front()

    {

        // return the next value in the sequence

    }

 

    void popFront()

    {

        // move the front of the sequence to the next value

    }

 

    bool empty()

    {

        // true if the range has no more values to return

    }

}

Para entender o poder desta interface simples, vamos passar por um exemplo. Digamos que queríamos escrever um programa que contenha todos os funcionários de uma empresa, excluindo os menores de 40 anos, e agrupamos o restante em um vetor de vetores de acordo com sua organização.

struct Employee

{

    uint id;

    uint organization_id;

    string name;

    uint age;

}

 

struct Employees

{

    Employee[] data;

 

    this(Employee[] employees)

    {

        data = employees;

    }

 

    Employee front()

    {

        return data[0];

    }

 

    void popFront()

    {

        data = data[1 .. $];

    }

 

    bool empty()

    {

        return data.length == 0;

    }

}

Aqui os dados vêm de um construtor como um exemplo simples, mas podem vir de qualquer fonte, como um CSV ou um banco de dados.

Observe que este código não deve ser usado no código atual, pois em D, o array dinâmico básico também age como um range, de modo que qualquer algoritmo que aceite ranges também aceita arrays. No entanto, as matrizes estáticas não são consideradas ranges, pois a operação popFront é baseada na mutação do comprimento do alcance, o que é impossível com matrizes estáticas. Para obter um alcance de uma matriz estática, deve-se criar uma fatia contendo todos os seus elementos, assim:

int[4] array = [1, 2, 3, 4]; // not a range

array[]; // valid range

Agora que o intervalo (range) está definido, podemos preenchê-lo e escrever o código de filtragem.

void main()

{

    import std.algorithm.iteration : filter, chunkBy;

 

    Employees employees = Employees([

        Employee(1, 1, "George", 50),

        Employee(2, 3, "John", 65),

        Employee(3, 2, "David", 40),

        Employee(4, 1, "Eli", 40),

        Employee(5, 2, "Hal", 35)

    ]);

 

    auto older_employees = employees

        .filter!(a => a.age > 40) // lambdas in D use the => syntax

        .chunkBy!((a,b) => a.organization_id == b.organization_id);

}

Todos os algoritmos do std.algorithm trabalham com ranges para evitar o problema de reescrever funcionalidades comuns para cada projeto. std.algorithm implementa ordenações, filtros, mapas, reduções e muito mais.

Como a estrutura de Funcionários está de acordo com a definição do intervalo de entrada (input range), ela também pode ser usada em foreach loops, o que detecta automaticamente se o valor passado é um input range.

foreach(employee; employees)

{

    writeln(employee);

}

que é equivalente a

for(; !employees.empty; employees.popFront())

{

    writeln(employees.front);

}

 

Tipos de Ranges

O input range é apenas a forma mais básica de range, há também:

·      forward ranges

·      bidirectional ranges

·      random access ranges

·      output ranges

Cada um desses ranges representa uma maneira distinta de acessar os dados subjacentes. Ou, no caso do intervalo de saída (output range), uma maneira de enviar dados para outra fonte. Cada um desses tipos de intervalos lhe dá acesso a diferentes algoritmos na biblioteca padrão.

 

ATRIBUTOS E DECLARAÇÕES DE DEPURAÇÃO

 

Agora depurar é parte da sintaxe da linguagem. O código pode ser ativado ou desativado no tempo de compilação, sem o uso de macros ou comandos de pré-processamento. A sintaxe de depuração permite um reconhecimento consistente, portátil e compreensível de que o código-fonte real precisa ser capaz de gerar compilações de depuração e compilações de lançamento.

 

TRATAMENTO DE EXCEÇÃO

 

O modelo superior try-catch-finally é usado em vez de apenas try-catch. Não há necessidade de criar objetos falsos apenas para que o destruidor implemente a semântica finally.

 

SINCRONIZAÇÃO

 

A programação multithreaded está se tornando cada vez mais popular, e D fornece primitivas para construir programas com multithreaded. A sincronização pode ser feita tanto no nível de métodos quanto no nível de objetos.

synchronized int func() { ... }

As funções sincronizadas permitem que apenas um segmento por vez esteja executando essa função.

A instrução de sincronização coloca um mutex em torno de um bloco de declarações, controlando o acesso por objeto ou globalmente.

 

INSTALAÇÃO E USO

 

O compilador do programa D pode ser baixado direto do site Dlang ou até ser utilizado a partir de uma interface online disponível no site para programar, se a instalação foi feita o compilador deve estar preparado para rodar seus programas, que serão compilados pelo terminal do sistema operacional utilizado. No terminal há três comandos do compilador D prontos, o primeiro é dmd serve para compilação simples do programa gerando um executável no diretório atual, o segundo é rdmd que não somente compila mas executa diretamente o programa e o terceiro é dub servindo para um projeto contendo diversos programas de um todo em um único diretório, então o comando compila todas as dependências e gera um executável acessado a partir de dub build. Cada opção de compilação possui suas opções de controle e que podem ser acessadas a partir de –help sucedendo um dos três comandos.

 

Exemplos de código:

// Programa amostra D (peneira.d)                         

/* Sieve of Eratosthenes prime numbers */

 

import std.stdio;

 

void main()

{

    size_t count;

    bool[8191] flags;

 

    writeln("10 iterations");

 

    // using iter as a throwaway variable

    foreach (iter; 1 .. 11)

    {

        count = 0;

        flags[] = 1;

 

        foreach (index, flag; flags)

        {

            if (flag)

            {

                size_t prime = index + index + 3;

                size_t k = index + prime;

 

                while (k < flags.length)

                {

                    flags[k] = 0;

                    k += prime;

                }

 

                count += 1;

            }

        }

    }

 

    writefln("%d primes", count);

}

 

import std.stdio;

import std.algorithm;

import std.range;

 

void main()

{

    // Let's get going!

    writeln("Hello World!");

 

    // An example for experienced programmers:

    // Take three arrays, and without allocating

    // any new memory, sort across all the

    // arrays inplace

    int[] arr1 = [4, 9, 7];

    int[] arr2 = [5, 2, 1, 10];

    int[] arr3 = [6, 8, 3];

    sort(chain(arr1, arr2, arr3));

    writefln("%s\n%s\n%s\n", arr1, arr2, arr3);

    // To learn more about this example, see the

    // "Range algorithms" page under "Gems"

}

//sort lines

import std.stdio, std.array, std.algorithm;

 

void main()

{

    stdin

        .byLineCopy

        .array

        .sort!((a, b) => a > b) // descending order

        .each!writeln;

}

 

 

REFERÊNCIA

“Overview”; DLang. Disponível em: https://dlang.org/overview.html. Acesso em 19 de outubro de 2020.