5 coisas que eu não sabia de Ruby

Introdução

Diferença entre :Symbol e “String”

A primeira coisa que me deixou bem curioso e que começei a pesquisar um pouco mais sobre foi a questão de termos o Symbol e a String que teoricamente são muito parecidos.

Então quando usar um ou outro? Sempre utilizei mais Strings no geral porém será que tem algum momento que seria melhor eu ter utilizado um Symbol? Realmente a uma diferença dos dois ou é somente a sintaxe?

Bom, vamos ver com os exemplos abaixo:

:Symbol

a = :xunda
=> :xunda

b = :xunda
=> :xunda

a.object_id
=> 1154268

b.object_id
=> 1154268

Como pode ver acima através do método object_id conseguimos saber exatamente a posição em memória de cada objeto, no caso as variáveis a e b que são os symbols com os mesmos caracteres.

Então concluimos que Symbols com os mesmos caracteres mesmo que em diferentes variáveis ou qualquer outro lugar no código são um único objeto

“String”

a = "xunda"
=> "xunda"

b = "xunda"
=> "xunda"

a.object_id
=> 6990405680384

b.object_id
=> 6990405442200

Já com as strings o resultado não é o mesmo, vemos que os números de onde foi alocado o espaço em memória para cada string são diferentes logo não se trata do mesmo objeto.

Então duas Strings mesmo tendo os mesmos caracteres são dois objetos diferentes

Bom sabendo sobre essa diferença conseguimos concluir que toda vez que criar uma nova string embora seja os mesmos caracteres estará instanciando um novo objeto em memória e ocupando mais um novo espaço em memória.

E quando tiver trabalhando com symbols caso seja os mesmos caracteres você esta economizando espaço em memória já que não instancia o objeto novamente apenas utiliza o mesmo objeto já instanciado em memória então não consome mais memória.

Então vou utilizar somente os maravilhos Symbols e ser feliz para sempre???

Gif Jim Carrey?

Humm, acho que não…

Vamos ver abaixo um pouco mais sobre essa questão de armazenamento em memória

Garbage Collector

https://www.infoq.com/br/presentations/entendendo-garbage-collector-ruby

Falar o que é Garbage Collector

Explicar que o Symbol apesar de ser mais rápido e ocupar menos espaço em memória não é coletado pelo garbage collector o que pode causar um sistema mais lento caso tenha diversos Symbols em memória que ficam alocados em memória “para sempre”

Conclusão

Use os saiba usa-los de acordo com cada caso

Cuidado com Floats

Uma coisa que no próprio livro alertou e que até então num tinha visto que acontecia esse tipo de coisa com o Float.

Float

0.0004 - 0.0003 == 0.0001
=> false

0.0004 - 0.0003
=> 0.0010000000000000005

ish

WTF!?!?



Quando vi até lembrei desse video que já ficou famoso a algum tempo atrás kkk


Isso ocorre porque a representação de números reais usada é o IEEE-754, a forma padrão de representar números reais em binários, que não é somente utilizada pelo Ruby, mas também por C, Java e Javascript. Porém, este padrão possui um problema perigoso de arredondamento causando esse tipo de resultado.

Muitas pessoas por desconhecerem esse problema podem usar floats para valores financeiros, o que certamente vai ocosionar vários desses erros. Para isso, usa-se a classe BigDecimal, do conjunto de bibliotecas padrão do Ruby.

BigDecimal

require 'bigdecimal'
=> true

num1 = BigDecimal.new("0.0004")
=> #<BigDecimal:7f6961d47328, '0.4E-3',9(18)>

num2 = BigDecimal.new("0.0003")
=> #<BigDecimal:7f6961d98237, '0.3E-3',9(18)>

result = num1 - num2
=> #<BigDecimal:7f6961d83vc3, '0.1E-3',9(18)>

result.to_s('F')
=> "0.0001"

Apesar de ser muito mais lenta que floats, eles são precisos e se tratando de cálculos financeiros, performance na maioria dos casos não é um problema, se comparado com aplicações científicas.

Conclusão

Como visto no video o muleke errando todas as contas de matemática rsrs E quando perguntam para ele… Quem te ensinou?… Meu professor de ciências

Ai está o erro rsrs, sempre pergunte para o professor certo…

Se vai trabalhar com números com poucas casas decimais e não é necessário tanta precisão com certeza é melhor utilizar os Floats mesmo assim consegue ter mais performace e é mais comum a utilização. Mas caso for trabalhar com cálculos financeiros ou caso precise de uma boa precisão com números de várias casas decimais com certeza teras que utlizar BigDecimal.

Diferenças entre Proc e Lambda

Primeira diferença

Proc

show = proc { |x, y| puts "#{x}, #{y}" }
=> #<Proc:0x817487288372487@(irb):8>

show.call(1, 2)
1, 2
=> nil

show.call(1)
1,
=> nil

show.call(1, 2, 3)
1, 2
=> nil

Como podemos ver acima apesar de no Proc termos definido trabalhar com 2 parametros no caso x e y, podemos passar somente um parametro ou 3 parametros que ele irá imprimir o que conseguir sem nem reclamar rsrs.

Já com Lambda é um pouco diferente, vamos ver abaixo:

Lambda

show = lambda { |x, y| puts "#{x}, #{y}" }
=> #<Proc:0x8174872882137@(irb):4 (lambda)>

show.call(1, 2)
1, 2
=> nil

show.call(1)
ArgumentError: wrong number of arguments (1 for 2)
        from (irb):4:in `block in irb_binding`
        from (irb):6:in `call`
        from (irb):6
        from /home/rd/.rbenv/versions/2.2.2/bin/irb:11:in `<main>`

show.call(1, 2, 3)
ArgumentError: wrong number of arguments (3 for 2)
        from (irb):4:in `block in irb_binding`
        from (irb):7:in `call`
        from (irb):7
        from /home/rd/.rbenv/versions/2.2.2/bin/irb:11:in `<main>`

Fazendo o mesmo com um Lambda ele já reclama que não consegue executar caso não passe exatamente os parametros que definiu em sua criação.

Dessa forma vemos que o Proc não valida a quantidade de parametros passados pois permite passar inumeros parametros e ele tenta executar com o que foi passado.Já o Lambda não permite isso, ele valida se o número de parametros é exatamente o mesmo.

Bom essa é a primeira diferença entre eles, mas vamos a segunda:

Segunda diferença

Proc

Bom quando temos um Proc dentro de um método e dentro do Proc há um return, esse return para a execução do método associado ao Proc fazendo com que a linhas após o return do Proc não sejam executadas. Como pode ver no exemplo abaixo:

def proc_stop
  puts "Cheguei..."
  proc { puts "Hey"; return; puts "Ho!" }.call
  puts "Saindo..."
end

proc_stop # Cheguei...; Hey

Lambda

Já com o Lambda mesmo tendo um return ele somente executa o return no contexto do Lambda. Dessa forma as linhas de código do método após o Lambda são executadas como pode ver nesse exemplo:

def lambda_stop
  puts "Cheguei..."
  lambda { puts "Hey"; return; puts "Ho!" }.call
  puts "Saindo..."
end

lambda_stop # Cheguei...; Hey; Saindo...

Conclusão

Agora que sabemos essas duas diferenças entre os dois podemos usar essas particuliaridades de cada um de acordo com a necessidade do nosso código.

Constantes não são constantes

Quando começei a ver sobre as constantes no Ruby que tinha a intenção de realmente não ser alterada e me deparei com isto:

CONSTANT = [:a, :b]
=> [:a, :b]

CONSTANT << :c
=> [:a, :b, :c]

Bom na verdade mesmo definindo como uma constante eu posso altera-la a qualquer momento assim como uma váriavel, minha primeira reação quanto a isso foi esta…

ish

Com um pouco de curiosidade para saber se de alguma forma eu poderia fazer com o que a constante não pudesse ser alterada. Tentei fazer isso dessa forma…

CONSTANT = [:a, :b].freeze
=> [:a, :b]

CONSTANT << :c
RuntimeError: can't modify frozen Array
       from (irb):2
       from /home/rd/.rbenv/versions/2.2.2/bin/irb:11:in `<main>`

CONSTANT
=> [:a, :b]

E dessa forma com o método freeze eu não posso alterar nesse caso o array colando mais valores nele e tudo mais…

Achei que tinha descoberto como realmente ter uma constante que nunca poderá ser mudada rsrs

Mas ao fazer mais experimentos, consegui fazer isso…

Aqui sem o freeze:

CONSTANT = "xunda"
=> "xunda"

CONSTANT = "blah"
(irb):2: warning: already initialized constant CONSTANT
(irb):1: warning: previous definition of CONSTANT was here
=> "blah"

CONSTANT
=> "blah"

E com o freeze:

CONSTANT = "xunda".freeze
=> "xunda"

CONSTANT = "blah"
(irb):2: warning: already initialized constant CONSTANT
(irb):1: warning: previous definition of CONSTANT was here
=> "blah"

CONSTANT
=> "blah"

O que na verdade quando reiniciamos a constante ela até avisa que você está redefinindo uma constante mas muda o valor da mesma forma com freeze ou sem freeze nesse caso o comportamento é o mesmo.

Conclusão

O Ruby não o proibe de modificar as constantes dependendo do tipo do objeto ele te avisa que está modificando uma constante porém deixa livre para muda-la.

Ele trata essa questão muito mais na parte conceitual mesmo do que até na pratica.

Eu consigo fazer com que uma constante não seja alterada porém não consigo fazer com que ela não seja reinicializada

! Pesquizar alguma formar de deixa-la inalterada porque o Jonatas falou que tem um jeito sim

Precedência de operadores booleanos

Quando começei a ver Ruby e vi que tinha o and, &&, or e || a principio pensei que o and e && fossem iguais, assim como o or e || também.

Porém vendo um pouco mais sobre Ruby descobri que muito pelo contrario, são bem diferentes!

Vamos ver a seguir ;)

and != &&

&&

do_something = "123"
=> "123"

do_other_stuff = "abc"
=> "abc"

if var = do_something && do_other_stuff
  puts "var is #{var}"
end
var is abc
=> nil

No caso do && o Ruby faz a comparação entre as duas variáveis dessa forma:

var = ( do_something && do_other_stuff )

Por isso ele retorna o var como abc neste caso, por que ele verifica as duas e retorna a última no caso da duas serem true.

and

do_something = "123"
=> "123"

do_other_stuff = "abc"
=> "abc"

if var = do_something and do_other_stuff
  puts "var is #{var}"
end
var is 123
=> nil

Já com o and é bem diferente, como vemos acima ele retorna a variavel como 123 porque faz esse tipo de execução:

( var = do_something ) and do_other_stuff

Então ele já inicia sua execução atribuindo o do_something ao var desse forma ele já retornado porque os dois são diferentes.

E com o or e || acontece a mesma coisa, a execução segue o mesmo padrão, veja abaixo…

||

var = ( do_something || do_other_stuff )

or

( var = do_something or do_other_stuff )

Vendo isso a minha primeira conclusão foi em sempre utilizar o &&. e ||. E esquecer que existe o and e or senão… kkk

Avdi Grimm

Inconformado com a falta de utilidade que tinha o and e or no Ruby começei a pesquisar mais sobre esse assunto porque não acreditava que não tinha nenhuma utilidade e queria saber como utilizar e quando é melhor utilizar do que o && e ||.

E nessa busca achei um post de um cara bem conhecido da comunidade Ruby o Avdi Grimm e ele nesse post Using “and” and “or” in Ruby.

Nesse artigo ele explica bem a diferença dos operadores e que o and e or do Ruby vieram do Perl. E no Perl eles tem o objetivo de serem utilizados para controlar o fluxo da aplicação e assim como o if e unless.

do_something() or die "It didn't work"

No Ruby eles tem esse mesmo proposito de controlar o fluxo da operação e não de ser um operador booleano.

Bom, vamos ver mais alguns exemplos sobre como utilizar para controlar o fluxo da operação

or

No exemplo abaixo, podemos ver que é possivel utilizar o or tendo a primeira condição com um retorno boolean neste caso o post.have_posts? e logo após a ação que ele irá fazer. Então se essa primeira condição retornar true ele nem executa a segunda ação e caso retorne false ele a executa.

Nesse caso teria mais um sentido de faça isso ou faça aquilo

class Post
  def have_posts?
    false
  end
end

post = Post.new
post.have_posts? or raise "Não tem posts!"
#=> RuntimeError: Não tem posts!

O comportamento é muito similiar a ter um unless como abaixo:

raise "Não tem posts!" unless post.have_posts?
#=> RuntimeError: Não tem posts!

and

Utilizando o and o comportamento é igual na questão de precedência a diferença é que para ele executar a segunda ação a primeira tem que retornar true

Nesse caso o sentido é mais de faça isso e aquilo

class Post
  def initialize(name)
    @name = name
  end

  def publish!
    puts "Post #{@name} publicado!"
  end
end

post = Post.new("5 dicas de Ruby") and post.publish!
#=> Post 5 dicas de Ruby publicado!

Nesse caso, o comportamente é igual ao utilizar um if como abaixo:

post.publish! if post = Post.new("5 dicas de Ruby")
#=> Post 5 dicas de Ruby publicado!

No rails

Um exemplo legal para utilizar o and no rails seria esse:

post = Post.find_by_name("5 dicas de Ruby") and post.publish!
#=> Post 5 dicas de Ruby publicado!

Onde ele busca o post e caso o encontre ele publica ;)

Conclusão

Sobre o uso de and e or certamente é não utiliza-los no lugar de && e || pois como vimos acima são diferentes então sabendo disso é possivel tirar melhor proveito deles para dependendo do caso ter um código mais legivel.