2 Sintaxe e Variáveis

Introdução

Neste capítulo, vamos aprender, principalmente, a estrutura de um programa em R, e os tijolinhos que a compõe.

Haverão dois aprendizados principais: (i) a estrutura pode ser entendida como a de um texto instrucional; (ii) temos apenas dois tijolos, (nomes de) objetos e (chamadas de) funções; (iii) como é a relação entre variáveis e a memória do computador. Esses tópicos principais são bastante teóricos/abstratos, mas serão úteis no futuro, e seu entendimento gera a habilidade de generalizar esses conceitos para outras linguagens de programação.

Aprenderemos também tópicos mais concretos: (i) a sintaxe básica da linguagem; (ii) como definir e como funcionam as variáveis; (iii) as funções (operadores) básicas da linguagem.

2.1 Sintaxe e Tijolinhos

Em termos simplistas, um script é um arquivo de texto com instruções a serem executadas por um computador.

Note que o computador, no nível mais baixo, apenas entende sequências de 0s e 1s, de modo que um script precisa ser “traduzido”. Algumas linguagens são compiladas, onde o texto inteiro é traduzido antes de serem executado, outras são interpretadas, traduzidas “na hora”, frase a frase. O R é o segundo caso, por isso que conseguiremos rodar códigos linha a linha – em vez de apenas poder executar um arquivo inteiro.

Um script é um texto instrucional como qualquer outro, escrito em um tipo de linguagem especial, “de programação”, mas é um texto. Similar à receita de bolo de cenoura da minha vó, ou o roteiro da peça escolar onde interpretei, com maestria, a árvore #3.

Como em qualquer linguagem, temos um vocabulário à disposição, um conjunto de palavras (ou tokens) existentes, organizadas em categorias como substantivos, verbos, etc. Nós combinamos as palavras em frases (ou statements/declarações) para descrever as instruções. Por fim, podemos opcionalmente organizar o texto em parágrafos (ou blocks), conjuntos de frases que devem ser lidas juntas, para definir a estrutura e facilitar o entendimento do texto.

Ok, a receita da minha vó não tinha parágrafos, a metáfora não é perfeita, paciência.

A seguir, vamos descrever com mais calma esses conceitos de tokens, statements e blocks.

2.1.1 Objetos e Funções

Existem dois tijolinhos principais no R:

Objetos

  • valores (dados), que podem carregar também atributos (metadados).
  • Cada valor tem um tipo/type36.
  • Podem ser associados à um nome/symbol. O conjunto nome-objeto é chamado de variável. Mas também podem ser escritos “por extenso”, como 1 ou "Hello World!".

em ciência da computação, o termo “objeto” é relacionado à Programação Orientada ao Objeto, significando “uma instância de uma classe”. Esse conceito irá existir no R, mas não é o que estamos tratando aqui. Aqui, “objeto” é um termo mais genérico.

Funções

  • Definem operações à serem realizadas usando objetos.
  • Operadores, símbolos como + e >, são uma categoria de função especial, que apresentam uma sintaxe mais enxuta.

Note que descrevemos tudo que há na linguagem em apenas dois conceitos, objetos e funções. Em outras palavras:

“[In R,] everything that exists is an object; everything that happens is a function call.” — John Chambers.

É esse tipo de simplicidade que, na minha opinião, torna aprender a “teoria” sobre o R tão interessante:

  • Veremos que ter essa categoria de conhecimento sobre a linguagem facilitará muito o entendimento de temas futuros.
  • Estaremos adquirindo a habilidade de aprender o funcionamento de linguagens de programação no geral, usando um exemplo (R) simples.

O capítulo 3 explicará os objetos, e 6 as funções. Pela lógica, esses dois devem conter a completude do conhecimento sobre R, e o resto do livro é só enfeite.

2.1.2 Palavras

Voltando à nossa analogia, tudo que você lê em um script é uma de duas (três) coisas.

Vocabulário

Quais palavras/tokens, temos disponíveis na linguagem R?

  • Substantivos, os nomes de objetos (ou objetos escritos por extenso).
  • Verbos, as (chamadas de) funções.

Para ser preciso, também tempos alguns caracteres puramente estéticos, syntactic sugar, como espaços e tabs.

Mais para frente, veremos que podemos salvar excertos de código R em um objeto, e teremos apenas três tipos de objetos da linguagem.

Mas vamos sair da abstração. Como escrevemos essas palavras em um texto de R?

  • Valores:
    • Strings: utilize aspas duplas " ou aspas simples': "Hello World!", 'Hello World!'. A galerinha do R costuma usar mais as duplas..
    • Números: simplesmente escreva-os: 1. Use um ponto . como o separador decimal 0.01.
    • Booleanos: escreva as palavras especiais TRUE e FALSE.
  • Variáveis: o operador <- associa o nome à esquerda ao valor à direita: a <- 1. Não se preocupe muito com isso por agora.
  • Funções: escreva o nome da função, e os argumentos que ela receberá entre parênteses sum(1, 1). Não se preocupe muito com elas por agora.
    • Operadores: são imputados como 1 + 1.
    • Parênteses podem ser utilizados em operações múltiplas: (1+1)/2.
  • Comentários: texto que não será avaliado como código. Use o símbolo #, que torna tudo após dele, na mesma linha, um comentário.

2.1.3 Frases

Como dito, as frases no R serão combinações de tokens. Qualquer combinação. Podem ser apenas um valor (1), o nome de uma variável (a), ou a chamada de uma função (sum(1, 1)).

cada linha do código abaixo é uma frase:

1
1 + 1
a <- 1
a #retorna o valor da variável "a"
sum(1, 1)
sum(1, 1) + a

E quanto a um código como 1 +? Bom, esse código não é sintaticamente correto. Normalmente, chamamos de statement/declaração apenas as frases sintaticamente corretas, e eu irei passar a usar essa nomenclatura em diante.

2.1.4 Parágrafos

Um mesmo texto pode ser organizado de várias maneiras diferentes, e isso é verdade para um script também. Como delimitamos as declarações?

  • Por padrão, uma declaração acaba na quebra de linha.
    • Se a declaração terminar inacabada (como no exemplo 1 +), o R ignora a quebra de linha, e tenta completar a declaração com a linha seguinte.
    • Um ponto e vírgula pode ser usado para delimitar uma declarações explicitamente: 1 + 1; 2 + 2. Mas seu uso não é uma comum nem recomendável.
  • Várias declarações podem ser agregadas em grupos ou blocks usando chaves {}.

um grupo de declarações é uma declaração em si. As chaves são uma função, que combina várias declarações em uma só. Uou, tudo realmente é uma chamada de função.

Note que, assim como em um texto, a organização em parágrafos não necessariamente afeta a lógica do texto, o que o script faz, pode afetar apenas sua organização.

Antes de avançar, vou mentir um pouco e dar duas definições, mas que serão melhor detalhadas no futuro.

Expressão

É uma declaração não avaliada, “congelada”, que o R não rodou. Elas terão seu próprio tipo de objeto.

Ter um tipo de objeto para isso é uma característica definidora do R, usaremos padrões de programação que operam sobre a própria linguagem.

Função

É uma expressão, que depende de variáveis, possivelmente associada à um nome. Utilizar esse nome, indicando os valores das variáveis envolvidas (entre parênteses ()), avalia a declaração, retornando seu resultado. Elas terão seus próprios tipos de objeto (mais de um).

Veja o exemplo abaixo, mas não se preocupe, irei detalhar mais sobre funções no capítulo 6. Mas note como salvamos uma função da mesma maneira que salvamos qualquer outro objeto.

liar <- function(x, y) {x + y}
liar(x = 1, y = 2) #> 3
liar(1, 2) #equivalente à declaração anterior

Com essa definição em mente, na nossa analogia do começo do capítulo, funções são “parágrafos nomeáveis”.

2.2 Variáveis

As variáveis merecem mais da nossa atenção. Mas não é para elas se acharem demais, valores/atributos e funções terão seus próprios capítulos.

2.2.1 Definindo Variáveis

Para definir variáveis, escrevemos seu nome, = ou <-, e a declaração que definirá seu valor e atributos: x = 1 + 1, x <- 1 + 1.

Também podemos usar a função assign(), mas que será explicada no futuro.

Ao rodar algo como x <- 1, o objeto 1 é salvo na memória do computador, e associado ao nome x. Como dito anteriormente, a variável é o conjunto objeto-nome.

O diagrama abaixo, inspirado no presente em Advanced R, ilustra esse processo. Note que o objeto existe na memória do computador, em uma localidade específica, e o nome é apenas uma referência à ele.

O objeto pode ser pensado como a localidade na memória em si, ou o que reside nela. Portanto, no diagrama, o objeto tem uma etiqueta, com o seu endereço de memória37.

2.2.2 Copy on Modify

2.2.2.1 Criando Referências Repetidas

Com base no que aprendeu, o que acontece ao rodar y <- x? Temos duas opções:

  1. y pode ser um novo nome, associado ao mesmo objeto – mesma posição na memória do computador – que x.
  1. y pode ser um novo nome, associado a um novo objeto – nova posição na memória do computador – que x, mas carregando a mesma informação.

Note que sempre temos duas variáveis, pois são dois pares nome-objetos diferentes.

O que acontece no R é o primeiro caso. Isso evita que se separe um novo lugar na memória quando não se é necessário, economizando espaço e tempo.

2.2.2.2 Modificando Referências Repetidas

Agora, o que acontece com x quando modificamos y, ou vice versa? Por exemplo, y <- 2? Novamente, temos duas opções:

  1. O objeto associado à y é modificado diretamente em seu local da memória, de modo que x também “verá” a mudança.
  1. O objeto é antes copiado para um novo local de memória, para então ser modificado apenas lá, de modo que x não “veja” a mudança.

O nome do comportamento no caso 1 é modify-in-place.

No R, o que acontece é o caso 2. Esse processo, de apenas “separar”/“copiar” objetos quando modificados, é chamado de copy-on-modify. Também falamos que os objetos no R, fora as exceções descritas abaixo, são imutáveis.

Copy-on-modify

No R, um mesmo objeto pode ter ser referenciado a mais de um nome, gerando várias variáveis. Modificar alguma delas não modifica o objeto diretamente, mas copia-o em um novo local na memória, para abrigar o objeto modificado. Isso é, os objetos são copiados-após-mudanças.

Existem duas exceções: objetos com apenas uma referência/um nome, e ambientes38. Esses são alterados “na hora”/“no lugar”, ou modify-in-place.

A melhor maneira de ver quando um objeto é copiado é olhando empiricamente. Veja a função tracemem() na seção 2.3.1 do Advanced R.

2.2.2.3 Especificidades

Diferentes tipos de valores terão diferentes relações com esse processo, mas vou poupá-los disso. O importante é saber que, na maioria dos casos, alterar y não altera x e vice versa, independente da complexidade do objeto.

se você está familiar com o conceito de lista, veja como elas se comportam em Advanced R seção 2.3.3. Em termos simplistas, listas são coleções de referências a objetos, e não coleções de objetos (diretamente).

este tema é complexo, e foi bastante simplificado. Vide Advanced R, seções “2.3 - Copy-on-modify” e “2.5 - Modify-in-place”. Os exemplos utilizando a função tracemem() são especialmente úteis.

2.2.3 Outras Características

Note que = e <- são muito similares, mas = serve para mais coisas que somente definição de variáveis, como indicar argumentos em uma função. Portanto, <- funciona como “definidor” em mais contextos, e é uma má prática utilizar = como definidor.

Ambos podem definir várias variáveis de uma vez: x = y = 3, x <- y <- 3, x = y <- 3.

Podemos usar também -> da esquerda pra direita: 3 -> x.

Existe um terceiro operador <<-, que será discutido no capítulo 6.

2.2.4 Regras de Nomenclatura

Nem toda combinação de caracteres pode ser um nome de variável. As principais regras são:

  • Nomes podem conter letras, números, “.” e “_“.
  • São case-sensitive.
  • Podem começar apenas com letras ou “.”.
  • Não podem ser palavras reservadas como “TRUE”.

Nomes não sintáticos podem ser definidos, se escritos usando crases (backticks): `_x` <- 1. Você provavelmente encontrará isso ao importar dados que não foram criados no R.

em muitos momentos, o R converte nomes não sintáticos utilizando a função make.names(). Você aprenderá sobre ela nos exercícios. É muito importante estar atento à esse comportamento, uma vez que é causa comum de erros.

Você verá que isso é um tema comum: o R tenta facilitar muitas tarefas, fazendo as coisas por você. Isso por um lado é o que o torna fácil de sair trabalhando, mas é causa de inconsistências.

2.3 Operadores

A princípio, deixaria os detalhes sobre operadores para os exercícios, mas fiquei com medo deles se sentirem excluídos.

Abaixo estão os operadores relevantes para o momento, suas descrições, e seu uso. Clique nos links dos operadores para abrir suas páginas de ajuda.

Tabela 2.1: Principais operadores do R.
Categoria Operador Descrição Uso
aritmétrica + soma num + num
aritmétrica - subtração num - num
aritmétrica * multiplicação num * num
aritmétrica / divisão num / num
aritmétrica ^ exponenciação num ^ num
aritmétrica %% divisão inteira num %% num
aritmétrica %/% resto da divisão num %/% num
comparação == igual x == y
comparação != diferente x != y
comparação \< menor que num < num
comparação > maior que num > num
comparação >= maior igual num >= num
comparação <= menor igual num <= num
lógica ! “não” lógico ! logi
lógica & “e” lógico logi & logi
lógica “ou” lógico logi │ logi
agrupadores { agrupador chaves { expr }
agrupadores ( agrupador parênteses ( expr )

na coluna de “Uso”, “logi” se refere a qualquer valor que se comporte como um valor booleano, “num” a qualquer valor que se comporte como número, “expr” à qualquer statement, e “x”/“y” à valores mais genéricos, ou à nomes de variáveis.

Agora vou apresentar a ordem de precedência da aplicação dos operadores. Associatividade se refere à como são resolvidos “empates”, “direita pra esquerda” significa que o operador mais à direita é analisado antes. Tudo ficará mais claro no exemplo que segue.

Tabela 2.2: Precedência dos operadores no R.
Operador Descição Associatividade
(), {} agrupadores dir. → esq.
::, ::: acessar namespaces (*) dir. → esq.
$, @ extração de components/slots (*) esq. → dir.
[, [[ indexação (*) esq. → dir.
^ exponenciação esq. → dir.
-, + (unário) mais e menos unários esq. → dir.
: sequências (*) esq. → dir.
%%, %/%, %xyz%, │> operadores especiais (*) esq. → dir.
*, / multiplicação, divisão esq. → dir.
+, - (binário) adição, subtração esq. → dir.
>, >=, <, <=, ==, != comparações esq. → dir.
! “não” lógico esq. → dir.
&, && “e” lógico (*) esq. → dir.
, ││ “ou” lógico (*) esq. → dir.
~ (unário e binário) fórmula (*) esq. → dir.
->, ->> definição pra direita (*) esq. → dir.
<-, <<- definição pra esquerda (*) esq. → dir.
= (definidor) definição pra esquerda dir. → esq.

linhas com um “(*)” apresentam operadores que ainda não foram apresentados.

para deixar mais claro, na declaração (3+6)/(1+2)/5^4 o seguinte ocorre:

  • () são analisados primeiro, porque tem precedência (estão acima na tabela).
    • Como é da direita pra esquerda, primeiro (1+2) vira 3, e depois, (3+6) vira 9.
  • ^ é analisado a seguir, logo, 5^4 vira 625.
  • / é analisado a seguir.
    • Como é da esquerda pra direita, primeiro 9/3 vira 3, e depois 3/625 vira 0.0048.

Vide Working With R, capítulo “6 - Basic Operations” para mais detalhes e exemplos. Cuidado, alguns conceitos utilizados lá não foram vistos ainda.


Complemento

Recapitulando

Sintaxe

Neste capítulo, vimos a estrutura geral de um script:

  • Temos palavras/tokens, frases/statements/declarações, e parágrafos/blocks.
  • No R, tudo que existe é um objeto, e tudo que acontece é uma chamada de função.
    • Objetos serão o tema do próximo capítulo, e funções, do capítulo 6.
    • Vimos como imputar cada token.
  • As declarações são combinações de tokens.
    • Costumam ser delimitadas por quebras de linha.
  • Declarações podem ser organizadas em parágrafos vias chaves.
  • Scripts são sequências de declarações.

Vimos definições inciais para dois conceitos importantes:

  • Uma expressão é uma declaração ainda não avaliada.
  • Uma função é uma expressão, que depende de variáveis, associada à um nome.
    • Funções podem ser entendidas como “parágrafos nomeáveis”.

Variáveis

Também demos uma atenção extra ao conceito de variável:

  • Variáveis são um objeto com um nome associado.
  • Aprendemos os operadores que definem variáveis = e <-, e porque <- é preferível.
  • Vimos características como x <- y <- 3 e as regras de nomenclatura.

Bem como alguns conceitos mais técnicos e avançados:

  • A dinâmica da memória no R é pautada pelo conceito de copy-on-modify. No R, um mesmo objeto pode ter ser referenciado a mais de um nome. Modificar algum deles não modifica o objeto original, e sim copia-o, criando um novo objeto. Isso é, os objetos são copiados-após-mudanças.
    • As exceções são objetos com apenas uma referência, e ambientes, que usam modify-in-place (são modificados “no lugar”).

Operadores

Por fim, apresentamos os operadores básicos, seu uso, e ordem de preferência. Foi dito que operadores são funções, apenas com sintaxe diferente.


Dicionário de Funções

Abaixo segue a lista de funções vistas neste capítulo.

Categoria Função Descrição
operadores +, -, *, /, \^, %%, %/% aritimétrica
operadores ==, !=, <, >, >=, <= comparação
operadores !, &, lógica
operadores {, ( organização de expressões
operadores <-, ->, =, definição de variáveis
variáveis assign(), remove(), rm() definição e remoção de variáveis
variáveis make.names() criar nomes sintáticos
ajuda help(), vignette(), ? ajuda sobre funções e pacotes

Referências

As referências principais deste capítulo são:

para outros temas não contidos aqui, mas fortemente relacionados com esse capítulo, vide Advanced R seções “2.4 - Object Size” e “2.6 - Unbinding and the Garbage Collector”.


Exercícios

os exercícios usam variáveis de mesmo nome. Garanta que você está utilizando as definições corretas. É necessário ver as páginas de ajuda das funções. Rode ?nome_na_funcao no console.

Sintaxe

  1. Considere o código abaixo, e estilize seus componentes de acordo com sua natureza, e explique seu raciocínio, conectando com os conceitos vistos em aula.
  • Fonte de (nomes de) variáveis em verde.
  • Fonte de valores em laranja.
  • Grifo de chamadas de funções em vermelho.
a <- 1
2
a
sum(a, 3)
sum(a, 3) + a

Variáveis

  1. Explique a diferença entre o objeto 1 e cada uma das variáveis abaixo.
1
a <- 1
b <- a
c <- a + 1
d <- b
e <- 1
  1. Foi comentado que objetos mais complexos têm comportamentos diferentes com relação ao processo de definição , mas todos seguem uma característica geral. Com base nisso, o que você espera que ocorra com b após a terceira linha de cada um dos código abaixo? Justifique, fazendo referência à essa tal característica geral.
a <- 1
b <- list(1, a)
a <- 2
a <- list(1, 2)
b <- a
a[[2]] <- 3

a função list() cria uma lista, que é uma coleção de valores. O operador [[ seleciona um elemento dela. Aprenderemos esses conceitos nos capítulos seguintes.

  1. Leia a página de ajuda da função make.names() para aprender as regras completas de definição de nomes, sobre como o R converte nomes não sintáticos. Preveja qual será o resultado das chamadas abaixo.
make.names("")
make.names("@")
make.names("TRUE")
make.names("`TRUE`")
make.names(c("a", "a", "a", "b", "b"), unique = TRUE)

Operadores

  1. Parta de um número qualquer x, por exemplo, x <- 5. Use os ensinados operadores para criar:
  • Uma frase que retorne TRUE se x for múltiplo de 2.
  • Uma frase que retorne TRUE se x não for múltiplo nem de 3, nem de 5.
  • Uma frase que retorne TRUE se a parte inteira da divisão de x por 4 é igual a 2, ou se seu quadrado for maior ou igual à 10.
  1. Liste a ordem das ações executadas no cálculo da expressão abaixo:
x <- FALSE | !5.2 %% 2 * 7 * 4 - -3 <= 100 & TRUE