Pular para o conteúdo principal

Clojure - Símbolos globais e locais

Até agora, sempre que foi necessário manter um valor em memória nós usamos o conceito de "variável", realizando a "definição de um símbolo" com o comando "def". Porém essa abordagem corresponde ao que conhecemos nas linguagens mais tradicionais como "definição de variável global".

Como é de conhecimento, as definições de "variáveis globais" podem trazer uma série de problemas as aplicações, uma vez que é quase impossível ter conhecimento de todas, com isso podemos acabar gerando problemas com a substituição de valores durante seu uso, podendo causar erros em diversos pontos da aplicação e tornando a aplicação instável e de difícil manutenção.

Dito isso, veremos como trabalhar com símbolos dentro de um escopo limitado, seguiremos os próximos exemplos dentro de nossas funções.

Escopo global

Como vimos até aqui, os símbolos de escopo global podem ser acessados de qualquer parte da aplicação, vamos ao exemplo, considerando uma aplicação hipotética de uma escola, temos duas rotinas distintas que mostram a média dos alunos em duas condições distintas:

  • Uma que mostra e média "padrão" acrescida de um ponto extra;
  • Outra que mostra a média do aluno em uma condição de "recuperação", que acresce a média dois pontos extras;

Enquanto a rotina que mostra a média "padrão" olha para um símbolo global para fazer seu cálculo sem fazer qualquer alteração a rotina que mostra a média dos alunos em condição de "recuperação" também utiliza o mesmo símbolo global, porém ela sempre altera seu valor para "dois".

O problema que teremos é o seguinte, enquanto a rotina que mostra as médias de alunos em "recuperação" não for executada a rotina de média "padrão", quando executada, apresentará um resultado correto, porém após primeira execução da rotina de "recuperação" a "padrão" começará a mostrar um resultado errado:

(def ponto-extra 1)

(defn verifica-situacao-aluno-padrao
  "Retorna a media do aluno somada a seu ponto extra"
  [nota1 nota2]
  (+ (/ (+ nota1 nota2) 2) ponto-extra)
)
(defn verifica-situacao-aluno-recuperacao
  "Retorna a media do aluno somada a seu ponto extra (em caso de recuperação)"
  [nota1 nota2]
  (def ponto-extra 2)
  (+ (/ (+ nota1 nota2) 2) ponto-extra)
)

ponto-extra

(verifica-situacao-aluno-padrao 5 5)

(verifica-situacao-aluno-recuperacao 5 5)

(verifica-situacao-aluno-padrao 5 5)

Exemplificando um problema com símbolo global

Sobre a imagem:

  • Na linha três definimos nosso símbolo global "ponto-extra";
  • Da linha cinco até a linha dezesseis definimos nossas duas funções, "padrão" e "recuperação" respectivamente;
  • Na linha dezenove checamos o valor do nosso símbolo global "ponto-extra", que é "1";
  • Na linha vinte e um consultamos a situação de um aluno com a rotina "padrão" e vemos que sua média é "6";
  • Na linha vinte e três consultamos a situação de um aluno com a rotina de "recuperação" e vemos que sua média é "7";
  • Na linha vinte e cinco consultamos a situação de um aluno com a rotina "padrão" novamente e agora vemos um resultado diferente, sua média passou a ser "7", mesmo valor que a rotina de "recuperação";
  • Na linha vinte e sete checamos o valor do nosso símbolo global "ponto-extra" novamente, e conforme esperado ele agora é "2";

Conforme já explicado, a função de "recuperação" altera o valor do símbolo global que é usado por outras funções, essa mudança não era esperada em outras partes da aplicação e isso gerou um problema de divergência de valores, e pior, a momentos onde o problema não ocorre (já que ele só ocorre a partir da primeira execução da rotina de "recuperação") e a rotinas que continuam a funcionar (a rotina "recuperaçãosempre funciona). Acabamos de gerar um bug em nossa aplicação que não será nada fácil de simular/encontrar/corrigir.

Escopo local

Como visto anteriormente, o uso inadequado de um símbolo global gerou um bug em nossa aplicação, para resolver esse problema o desenvolvedor que criou a função de "recuperação" deveria ter usado um símbolo de escopo local na rotina, uma vez que o valor de "ponto-extra" usado ali é uma regra especifica dessa rotina.

Para trabalharmos com o escopo local utilizaremos a função "let", com ele criamos um escopo local com símbolos e funções:

  • O "let" deve ser usado entre parênteses;
  • Ele recebe um vetor de símbolos/valores;
  • Ele recebe uma ou mais funções;
  • Ele retorna o resultado da execução de sua última função;
(let [idade 40] (+ idade 1))

Usando o let pela primeira vez

Na imagem acima:

  • Na linha um criamos nosso escopo local com o "let", nesse escopo temos o símbolo "idade" com o valor "40" dentro do vetor e temos uma função que soma a valor "1" ao nosso símbolo "idade";
  • Na linha dois temos o resultado da função de soma;
  • Na linha três tentamos usar o símbolo "idade" e recebemos um erro na sequencia dizendo que não foi possível resolver o símbolo;

Bom, agora que já sabemos como resolver o problema de escopo vamos aplicar uma correção em nossa função "recuperação", para que ela deixe de usar o símbolo global, primeiro vamos reiniciar o símbolo global para seu valor correto, em seguida vamos corrigir a função e ne sequencia executar os testes:

(def ponto-extra 1)

(defn verifica-situacao-aluno-recuperacao
  "Retorna a media do aluno somada a seu ponto extra (em caso de recuperação)"
  [nota1 nota2]
  (let [ponto-extra-recuperacao 2]
    (+ (/ (+ nota1 nota2) 2) ponto-extra-recuperacao))  
)

(verifica-situacao-aluno-padrao 5 5)

(verifica-situacao-aluno-recuperacao 5 5)

(verifica-situacao-aluno-padrao 5 5)

ponto-extra

Refatorando a função para uso do escopo local

Na imagem acima:
  • Na linha 1 redefinimos nosso símbolo global para seu valor correto;
  • Da linha três até a oito temos a refatoração de nossa função com o uso do "let";
  • Nas linhas dez, doze e catorze repetimos os testes, e agora os seus respectivos resultados estão corretos;

Conclusão

Nesse post entendemos e exemplificamos os problemas causados pelo uso do escopo global, vimos como trabalhar com escopo local com o "let" e refatoramos nossa função problemática para que nossa aplicação voltasse a funcionar da forma esperada.

Saiba mais

Comentários

Mais visitadas

Funções de data Oracle

  Com o Oracle é possível obter uma série de resultados a partir de uma ou mais datas, como por exemplo verificar o último dia do mês ou verificar a quantidade de meses entre duas datas, então vamos a alguns exemplos:   Data atual do sistema: SYSDATE Remover meses de uma data: ADD_MONTHS(SYSDATE, -1) Adicionar meses de uma data: ADD_MONTHS(SYSDATE, +1) Buscar o último dia do mês: LAST_DAY(SYSDATE) Primeiro dia do mês: TRUNC(SYSDATE, ‘MONTH’) Quantidade de meses entre duas datas: MONTHS_BETWEEN(SYSDATE, ‘27/07/1982’) Primeiro dia do ano: TRUNC(SYSDATE, ‘YEAR’) Dias da semana: DECODE( TO_NUMBER( TO_CHAR          (SYSDATE, ‘D’) ) ,1, ‘domingo’ ,2, ‘segunda-feira’ ,3, ‘terça-feira’ ,4, ‘quarta-feira’ ,5, ‘quinta-feira’ ,6, ‘sexta-feira’ ,7,’sábado’ )

Listar arquivos existentes em diretório (Delphi)

Mostraremos uma maneira simples e prática para listar o conteúdo de um diretório com a opção de incluir nessa listagem os arquivos de seus subdiretórios. No exemplo abaixo temos um Edit para receber o diretório a ser pesquisado um CheckBox para indicar se os subdiretórios entrarão na pesquisa um botão para efetuar a pesquisa e um Memo para listar os arquivos encontrados, no final um Edit que receberá o cálculo final (em bytes) da soma do tamanho dos arquivos. procedure TForm1.Button1Click(Sender: TObject); begin   tamanhoTotal := 0;   memLista.Lines.Clear;   ListarArquivos(edtDiretorio.Text, chkSub.Checked);   Edit1.Text := IntToStr( tamanhoTotal ); end; procedure TForm1.ListarArquivos(Diretorio: string; Sub:Boolean); var   F: TSearchRec;   Ret: Integer;   TempNome: string; begin   Ret := FindFirst(Diretorio+'\*.*', faAnyFile, F);   try     while Ret = 0 do       begin         if TemAtributo(F.Attr,

Como Verificar se um Objeto Existe (Delphi)

Em alguns momentos surge a necessidade de verificar se um determinado objeto existe, ou seja se já foi criado, principalmente quando se trabalha com criação dinâmica em tempo de execução, então vamos ao exemplo: - Vamos criar uma variável, um vetor do tipo caixa de texto: var Minha_caixa : array of TEdit; - Em seguida definir o tamanho desse vetor, no caso será dez: setLength(Minha_caixa, 10) - Agora iremos criar nossa caixa de texto: // lembrando que o vetor inicia em zero // logo o índice final é o tamanho total - 1 for vl_i := 0 to Length(Minha_caixa) -1 do begin Minha_caixa[vl_i] := TEdit.Create(self); with Minha_caixa[vl_i] do begin Parent := Self; Name := 'Caixa_N'+IntToStr(vl_i); Text := 'Esta é a '+IntToStr(vl_i)+' º caixa !'; ReadOnly := true; Height := 21; Width :=