Back-End

12 fev, 2009

Programação orientada a eventos e lambda function em ASP/VBScript

Publicidade

Olá amigos do iMasters. Neste artigo vou falar um pouco sobre alguns temas que julgo atuais e importantes para todo programador moderno. Entretanto os exemplos serão escritos em ASP/VBScript+JScript, pois assim sou coerente com o propósito dos artigos que me propuz a escrever, que é apresentar o ASP como um ambiente extremamente versátil e eficiente. Segue abaixo a relação dos artigos:

  1. ASP, uma tecnologia mal interpretada.
  2. Programação orientada a eventos e lambda function em ASP/VBScript.
  3. Linguagens: Baseada em Objetos e Orientada a Objetos.
  4. Orientação a Objetos em VBScript “Hackers way”.
  5. Scripting Components, os “Às” na manga.
  6. Caching, conceito de DRY(Don’t Repeat Yourself) aplicado ao ASP.

Se você estiver lendo um artigo meu pela primeira vez, recomendo enfaticamente que leia os anteriores primeiro, pois estou tentando levá-los à compreensão de uma grande abstração, contando uma pequena parte da idéia por vez. Colocarei links para os temas anteriores para facilitar o acesso, mas utilizem CTRL+Click, pois não há links de um artigo anterior para seus sucessores.

Programação orientada a eventos

A programação orientada a eventos, também conhecida como programação baseada em eventos, é um paradigma de programação, isto é, um estilo fundamental de se programar, no qual a execução do programa é afetada por eventos(geralmente detectados por sensores).

Esta forma de se programar é, na minha humilde opinião, a base de todos os sistemas de UI(User Interface) sofisticados. Por exemplo, aquela barrinha do MAC OSX que todos adoram e suas similares:

Ela nunca abre uma janela solicitando “Vá com o mouse para cima de um ícone e clique”, ela simplesmente possui um sensor que avisa “Olha, programa, detectei que o mouse entrou na posição (x,y) da barra”, ao receber esta mensagem o programa faz o zoom e mostra o label dos ícones na posição. Este parece um exemplo bobo, mas acho que ilustra bem a diferença entre a programação em lote e a programação orientada a eventos.

É importante para um programador web conhecer a programação orientada a eventos, pois uma das características mais marcantes da Web 2.0 é a grande quantidade de widgets que fazem parte das chamadas RIA(Rich Application Interface) que são aqueles programas com telas que você pensa “Que legal isso aqui!”.

Uma outra coisa importante a ser notada é que os paradigmas de programação não são mutuamente exclusivos, isto é, uma linguagem, e por conseqüência um programa, pode suportar múltiplos paradigmas. Um programa pode ser puramente em lote, puramente orientado a eventos, ou conter trechos de ambos os paradigmas. Pode-se, portanto, criar programas meio orientado a objetos/procedurais, meio orientado a eventos/lote. Um arquiteto de software experiente saberá qual o melhor paradigma a ser seguido para construir um determinado feature do programa.

Apesar desta forma de se programar poder ser executada em qualquer linguagem de programação, ela é facilitada por linguagens com noções funcionais como JScript, Lisp (a segunda linguagem mais antiga da nossa história), Haskell entre outras. Nos exemplos a seguir mostrarei como programar:

  1. Utilizando-se apenas VBScript.
  2. Fazendo um bridge simples com JScript para utilizar a noção de lambda que ela possui (um bom exercícío aqui é utilizar a noção de lambda do Python para alcançar o mesmo objetivo).

Nota: O segundo exemplo é um dos motivos pelos quais julgo o ASP extremamente versátil, pois ele mostra uma aplicação do conceito exposto no primeiro artigo.

Função lambda

Para aqueles que ainda não foram apresentados, a função lambda é um feature interessante que nasceu com o Lisp e permite você definir e utilizar funções em tempo de execução. Esta é uma das características que facilitaram o desenvolvimento de AI(Artificial Intelligence) (pense em um algorítimo recursivo que gera, interpreta e executa funções para gerar, interpretar e executar funções e vai aprendendo alguma coisa importante durante o processo).

Mãos à massa

Utilizando-se a classe CustomEvent(versão sem comentários) que escrevi para o AXE(ASP Xtreme Evolution), mas que funciona out-of-the-box e tem licensa MIT:

<%

class CustomEvent

public classType
public classVersion

public Owner
private Handlers
public Arguments

private sub Class_initialize()
classType = typename(Me)
classVersion = "1.0.0"

set Handlers = Server.createObject("Scripting.Dictionary")
set Arguments = Server.createObject("Scripting.Dictionary")
end sub

private sub Class_terminate()
Handlers.removeAll()
Arguments.removeAll()
set Handlers = nothing
set Arguments = nothing
end sub

public sub addHandler(fn)
select case typename(fn)
case "JScriptTypeInfo"
set Handlers.item(fn.toString()) = fn
case else
set Handlers.item(fn) = getRef(fn)
end select
end sub

public sub removeHandler(fn)
set Handlers.item(fn) = nothing
Handlers.remove(fn)
end sub

public sub fire()
dim fn : for each fn in Handlers
Handlers.item(fn)(Me)
next
end sub

public function revealArguments()
revealArguments = ""
dim arg : for each arg in Arguments
revealArguments = revealArguments & ", " & arg
next
revealArguments = mid(revealArguments, 3)
end function

end class

%>
<script language="javascript" runat="server">
function lambda(f) {
if(/^function\s*\([ a-z0-9.$_,]*\)\s*{[\S\s]*}$/gim.test(f)) {
eval("f = " + f.replace(/\r/g, '').replace(/\n/g, ''));
return f;
} else {
return function() {};
}
}
</script>

Você pode criar eventos nas suas classes como no exemplo de classe abaixo, que será utilizada nos exemplos 1 e 2:

<%

class ClassWithEvents
public classType
public classVersion

public onComplimentBefore' [1]
public onComplimentAfter

private sub Class_initialize()
classType = typename(Me)
classVersion = "1.0.0"

set onComplimentBefore = new CustomEvent : set onComplimentBefore.Owner = Me' [2]
set onComplimentAfter = new CustomEvent : set onComplimentAfter.Owner = Me
end sub

private sub Class_terminate()
set onComplimentBefore = nothing' [3]
set onComplimentAfter = nothing
end sub

public sub compliment(firstname, lastname, nickname)
onComplimentBefore.Arguments.item("firstname") = firstname' [4]
onComplimentBefore.Arguments.item("lastname") = lastname
onComplimentBefore.Arguments.item("nickname") = nickname

call onComplimentBefore.fire()' [5]
Response.write("Method compliment called." & vbNewline)
call onComplimentAfter.fire()
end sub

end class

%>

Exemplo 1

Utilizando a classe que possui eventos para criar objetos com eventos em VBScript:

<code><pre><%

sub ev_onComplimentBefore(ev)' [6]
Response.write("Event onComplimentBefore has been fired. I was really expecting this method to say: 'Hello World " & ev.Arguments.item("firstname") & " " & ev.Arguments.item("lastname") & " (" & ev.Arguments.item("nickname") & ")'" & vbNewline)
end sub

sub ev_onComplimentAfter(ev)
Response.write("Event onComplimentAfter has been fired" & vbNewline)
end sub

dim CwE : set CwE = new ClassWithEvents
call CwE.onComplimentBefore.addHandler("ev_onComplimentBefore")' [7]
call CwE.onComplimentAfter.addHandler("ev_onComplimentAfter")
call CwE.compliment("Fabio", "Nagao", "nagaozen")
set CwE = nothing

%></pre></code>

O código acima deve escrever na tela do seu navegador:

Event onComplimentBefore has been fired. I was really expecting this method to say: ‘Hello World Fabio Nagao (nagaozen)’

Method compliment called.

Event onComplimentAfter has been fired

Seguem abaixo as notas de rodapé [#] que deixei no código fonte:

  1. Definindo os eventos.
  2. Inicializando os eventos como tipo CustomEvent e atribuindo um ponteiro à instância da classe através da propriedade Owner.
  3. Liberando os eventos criados na etapa 2 da memória.
  4. Definindo os argumentos do evento.
  5. Avisando o sensor que ocorreu um evento.
  6. Configurando ações para um determinado evento.
  7. Configurando o sensor para observar um evento.

Exemplo 2

Ok, o exemplo 1 foi legal, mas a maioria das pessoas acostumadas com programação orientada a eventos vai reclamar daquelas subrotinas da nota [6]. O que é natural, pois aquilo é pouquíssimo intuitivo. Afinal de contas, se os eventos estão relacionados ao objeto e somente a ele, porque não os definir dentro do próprio objeto? Bom, isso é verdade, o problema é que o VBScript não possui nenhum método para fazer isso dentro do objeto. Aqui, novamente IMHO(In My Humble Opinion), as alternativas naturais seriam:

* ou extender a classe ClassWithEvents para outra classe que possuísse as definições das ações do evento e depois criar uma instância dessa nova classe, o que é impossível pela noção de objetos do VBScript;

* ou utilizar a noção de lambda function, que também não existe no VBScript.

Então, o que fazer? Se render às limitações expressivas da linguagem? Jamais! Afinal de contas, não adianta mudar de linguagem, pois qualquer linguagem sempre tem limitações, não é verdade?

A solução, dentro do ASP, é mais fácil do que se imagina. Você fala JScript? Python? Haskell? Nossa, o ASP também! Essas linguagens possuem uma capacidade expressiva funcional? Siiim! Pronto, resolvido o problema!

Explicando melhor: Para aqueles que ainda não perceberam, juntamente com a definição da classe CustomEvent, criei também uma função global em javascript (que é a mesma coisa que JScript) chamada lambda. Essa função recebe a definição de uma função, interpreta e retorna a função interpretada. Javascript é uma linguagem tão bonita que incorporou o conceito de lambda ao seu núcleo e portanto ela usa lambda sem ter uma palavra reservada para isso (diferentemente do Python). Esta função, dentro do ASP, não é global apenas para o JScript, ela é global para a aplicação inteira, isto é, você pode invocar ela do VBScript. Com essa nova função em mãos, podemos transformar o exemplo 1 no seguinte:

<%

dim CwE : set CwE = new ClassWithEvents
call CwE.onComplimentBefore.addHandler(lambda("function(ev){ Response.write('Event onComplimentBefore has been fired. I was really expecting this method to say: \'Hello World ' + ev.Arguments.item('firstname') + ' ' + ev.Arguments.item('lastname') + ' (' + ev.Arguments.item('nickname') + ')\'\r\n') }"))
call CwE.onComplimentAfter.addHandler(lambda("function(ev){ Response.write('Event onComplimentAfter has been fired') }"))
call CwE.compliment("Fabio", "Nagao", "nagaozen")
set CwE = nothing

%>

Que também imprime a mesma mensagem do exemplo 1 na tela do navegador. Convenhamos, ficou bem melhor agora, não? Muito mais intuitívo também.

Resumo da história

O paradigma de programação orientada a objetos aplicado juntamente com o de programação orientado a eventos, permite ao programador criar ambientes que se assemelham muito à forma que compreendemos os objetos e eventos no nosso mundo físico. Por exemplo, estamos acostumados a atravessar a rua quando o semáforo está verde e a parar quando está vermelho. Um programador, ao tentar modelar este ambiente, pode criar, por exemplo, as classes “Pedestre” e “Semáforo”. O “Pedestre” tem um método “.andarPara(local)” que diz para ele ir para algum local. Este método, possui um sensor “onSemáfaroVermelho” que aciona o procedimento “.parar”, quando o sensor do pedestre receber “onSemáfaroVerde” do “Semáforo”, então ele chama “.andarPara(local)” novamente.