É possível ter métodos dentro de métodos?

89

Eu tenho um método dentro de um método. O método interior depende de um loop variável que está sendo executado. Isso é uma má ideia?

Viagem
fonte
2
Você pode compartilhar o exemplo de código ou pelo menos um equivalente lógico do que você está tentando fazer.
Aaron Scruggs

Respostas:

165

ATUALIZAÇÃO: uma vez que esta resposta parece ter despertado algum interesse ultimamente, eu gostaria de apontar que há uma discussão sobre o rastreador de problemas Ruby para remover o recurso discutido aqui, ou seja, proibir ter definições de método dentro de um corpo de método .


Não, Ruby não tem métodos aninhados.

Você pode fazer algo assim:

class Test1
  def meth1
    def meth2
      puts "Yay"
    end
    meth2
  end
end

Test1.new.meth1

Mas esse não é um método aninhado. Repito: Ruby não tem métodos aninhados.

O que isto é, é uma definição de método dinâmico. Quando você executa meth1, o corpo de meth1será executado. O corpo apenas define um método chamado meth2, e é por isso que, depois de executar meth1uma vez, você pode chamar meth2.

Mas onde está meth2definido? Bem, obviamente não é definido como um método aninhado, uma vez que não existem métodos aninhados em Ruby. É definido como um método de instância de Test1:

Test1.new.meth2
# Yay

Além disso, obviamente será redefinido sempre que você executar meth1:

Test1.new.meth1
# Yay

Test1.new.meth1
# test1.rb:3: warning: method redefined; discarding old meth2
# test1.rb:3: warning: previous definition of meth2 was here
# Yay

Resumindo: não, Ruby não suporta métodos aninhados.

Observe também que em Ruby, corpos de método não podem ser fechamentos, apenas corpos de bloco podem. Isso praticamente elimina o principal caso de uso para métodos aninhados, já que mesmo se Ruby suportasse métodos aninhados, você não poderia usar as variáveis ​​do método externo no método aninhado.


ATUALIZAÇÃO CONTINUADA: em um estágio posterior , então, esta sintaxe pode ser reutilizada para adicionar métodos aninhados ao Ruby, que se comportariam da maneira que descrevi: eles teriam como escopo seu método de contenção, ou seja, invisível e inacessível fora de seu método de contenção corpo. E, possivelmente, eles teriam acesso ao escopo léxico do método de contenção. No entanto, se você ler a discussão que vinculei acima, poderá observar que matz é fortemente contra métodos aninhados (mas ainda para remover definições de métodos aninhados).

Jörg W Mittag
fonte
6
Você também pode mostrar, no entanto, como criar um lambda de fechamento em um método para DRYness ou como executar a recursão.
Phrogz
119
Estou sentindo que Ruby pode não ter métodos aninhados.
Mark Thomas
16
@Mark Thomas: Eu esqueci de mencionar que Ruby não tem métodos aninhados? :-) Sério: no momento em que eu escrevi essa resposta, já havia três respostas, cada uma das quais afirmavam que Ruby faz tem métodos aninhados. Algumas dessas respostas tiveram até votos positivos, apesar de estarem totalmente erradas. Um até foi aceito pelo OP, novamente, apesar de estar errado. O trecho de código que a resposta usa para provar que Ruby suporta métodos aninhados, na verdade prova o oposto, mas aparentemente nem os upvoters nem o OP realmente se preocuparam em verificar. Então, eu dei uma resposta certa para cada resposta errada. :-)
Jörg W Mittag
10
Quando você percebe que tudo isso são apenas instruções para o Kernel que modificam as tabelas e que os métodos, classes e módulos são apenas entradas nas tabelas e não são reais, você se torna como Neo quando ele vê a aparência da Matrix. Então você poderia realmente se tornar filosófico e dizer que além dos métodos aninhados, não existem nem mesmo métodos. Não há nem mesmo agentes. Eles são programas da matriz. Mesmo aquele bife suculento que você está comendo é apenas uma entrada em uma mesa.
mydoghasworms
3
Não há métodos, seu código é apenas uma simulação em The Matrix
bbozo
13

Na verdade, é possível. Você pode usar procs / lambda para isso.

def test(value)
  inner = ->() {
    value * value
  }
  inner.call()
end
SuperManEver
fonte
2
Você não está errado, mas sua resposta foi formulada como uma solução para obter métodos aninhados. Quando na realidade você está apenas usando procs que não são métodos. É uma boa resposta além de alegar resolver "métodos aninhados"
Brandon Buck
5

Não, não, Ruby tem métodos aninhados. Verifique isto:

def outer_method(arg)
    outer_variable = "y"
    inner_method = lambda {
      puts arg
      puts outer_variable
    }
    inner_method[]
end

outer_method "x" # prints "x", "y"
iirekm
fonte
9
método_interno não é um método, é uma função / lambda / proc. Não há instância associada de nenhuma classe, portanto, não é um método.
Sami Samhuri
2

Você pode fazer algo assim

module Methods
  define_method :outer do 
    outer_var = 1
    define_method :inner do
      puts "defining inner"
      inner_var = outer_var +1
    end
    outer_var
  end
  extend self
end

Methods.outer 
#=> defining inner
#=> 1
Methods.inner 
#=> 2

Isso é útil quando você está fazendo coisas como escrever DSLs que requerem compartilhamento de escopo entre métodos. Mas, do contrário, é muito melhor você fazer qualquer outra coisa, porque, como as outras respostas disseram, inneré redefinido sempre que outerfor invocado. Se você deseja esse comportamento, e às vezes deseja, esta é uma boa maneira de obtê-lo.

mdmoskwa
fonte
2

O jeito do Ruby é fingir com hacks confusos que farão alguns usuários se perguntarem "Como diabos isso funciona?", Enquanto os menos curiosos simplesmente memorizarão a sintaxe necessária para usar a coisa. Se você já usou Rake ou Rails, já viu esse tipo de coisa.

Aqui está um tal hack:

def mlet(name,func)
  my_class = (Class.new do
                def initialize(name,func)
                  @name=name
                  @func=func
                end
                def method_missing(methname, *args)
                  puts "method_missing called on #{methname}"
                  if methname == @name
                    puts "Calling function #{@func}"
                    @func.call(*args)
                  else
                    raise NoMethodError.new "Undefined method `#{methname}' in mlet"
                  end
                end
              end)
  yield my_class.new(name,func)
end

O que isso faz é definir um método de nível superior que cria uma classe e a passa para um bloco. A classe costuma method_missingfingir que possui um método com o nome que você escolheu. Ele "implementa" o método chamando o lambda que você deve fornecer. Ao nomear o objeto com um nome de uma letra, você pode minimizar a quantidade de digitação extra que ele requer (que é a mesma coisa que o Rails faz nele schema.rb). mleté nomeado após a forma Common Lisp flet, exceto onde fsignifica "função", msignifica "método".

Você o usa assim:

def outer
   mlet :inner, ->(x) { x*2 } do |c|
     c.inner 12
   end
end

É possível fazer uma engenhoca semelhante que permite que várias funções internas sejam definidas sem aninhamento adicional, mas que requer um hack ainda mais feio do tipo que você pode encontrar na implementação de Rake ou Rspec. Descobrir como o Rspec let!funciona levaria você a um longo caminho para ser capaz de criar uma abominação tão horrível.

Jogue fora conta
fonte
-3

:-D

Ruby tem métodos aninhados, só que eles não fazem o que você espera que façam

1.9.3p484 :001 > def kme; 'kme'; def foo; 'foo'; end; end              
 => nil 
1.9.3p484 :003 >   self.methods.include? :kme
 => true 
1.9.3p484 :004 > self.methods.include? :foo
 => false 
1.9.3p484 :005 > kme
 => nil 
1.9.3p484 :006 > self.methods.include? :foo
 => true 
1.9.3p484 :007 > foo
 => "foo" 
bbozo
fonte
4
Este não é um método aninhado ... veja a resposta de Jörg W Mittag para um entendimento claro.
Hardik