integração de objetos da linguagem c# com regras de produçãotg/2008-1/vcr2.pdf · praticamente...

47
Al Or luno rien I o: V nta In Vic ado nte ctor or: G eg r Ca Geb gr C ava ber ra C# alca r L. açã c anti Ram U G ão co i Ro ma Uni Grad Tr o om odr alho iver dua rab d m r rigu o rsid ação Ce ba de re ues dade o em ent alh e o eg s Re e Fe m C tro d ho ob gra ecif ede Ciên de I de bje as fe, eral ncia Info e G # et s d Jun de a da orm Gra # to de nho Per a Co máti adu # os e o de rna omp ica ua s d p e 20 amb put açã da pro 008 buco taçã ão a l od 8. o ão lin du ng gu çã vcr glr@ ua ão r2@ @c ag o @cin cin. gem n.u .ufp m ufpe pe.b m e.b br br

Upload: phamthuan

Post on 31-Dec-2018

218 views

Category:

Documents


0 download

TRANSCRIPT

Aluno: Orientador: Aluno: Orientador:

Integração de objetos da linguagem

Aluno: Victor Cavalcanti RodriguesOrientador:

Integração de objetos da linguagem

Victor Cavalcanti RodriguesOrientador:

Integração de objetos da linguagem

Victor Cavalcanti RodriguesOrientador: Geber

Integração de objetos da linguagem

Victor Cavalcanti RodriguesGeber

Integração de objetos da linguagem

C# com regras de produção

Victor Cavalcanti RodriguesGeber

Integração de objetos da linguagem

C# com regras de produção

Victor Cavalcanti RodriguesGeber L.

Integração de objetos da linguagem

C# com regras de produção

Victor Cavalcanti Rodrigues Ramalho

UGraduação em Ciência da Computação

Integração de objetos da linguagem

C# com regras de produção

Victor Cavalcanti RodriguesRamalho

Universidade Federal de PernambucoGraduação em Ciência da Computação

Trabalho de Graduação

Integração de objetos da linguagem

C# com regras de produção

Victor Cavalcanti RodriguesRamalho

niversidade Federal de PernambucoGraduação em Ciência da Computação

Trabalho de Graduação

Integração de objetos da linguagem

C# com regras de produção

Victor Cavalcanti RodriguesRamalho

niversidade Federal de PernambucoGraduação em Ciência da Computação

Centro de Informática

Trabalho de Graduação

Integração de objetos da linguagem

C# com regras de produção

Victor Cavalcanti Rodrigues

niversidade Federal de PernambucoGraduação em Ciência da Computação

Centro de Informática

Trabalho de Graduação

Integração de objetos da linguagem

C# com regras de produção

Victor Cavalcanti Rodrigues

Recife, Junho de 2008.

niversidade Federal de PernambucoGraduação em Ciência da Computação

Centro de Informática

Trabalho de Graduação

Integração de objetos da linguagem

C# com regras de produção

Recife, Junho de 2008.

niversidade Federal de PernambucoGraduação em Ciência da Computação

Centro de Informática

Trabalho de Graduação

Integração de objetos da linguagem

C# com regras de produção

Recife, Junho de 2008.

niversidade Federal de PernambucoGraduação em Ciência da Computação

Centro de Informática

Trabalho de Graduação

#Integração de objetos da linguagem

C# com regras de produção

Recife, Junho de 2008.

niversidade Federal de Pernambuco

Graduação em Ciência da ComputaçãoCentro de Informática

Trabalho de Graduação

#Integração de objetos da linguagem

C# com regras de produção

Recife, Junho de 2008.

niversidade Federal de Pernambuco

Graduação em Ciência da ComputaçãoCentro de Informática

Trabalho de Graduação

#Integração de objetos da linguagem

C# com regras de produção

Recife, Junho de 2008.

niversidade Federal de PernambucoGraduação em Ciência da Computação

Centro de Informática

Trabalho de Graduação

Integração de objetos da linguagem

C# com regras de produção

Recife, Junho de 2008.

niversidade Federal de PernambucoGraduação em Ciência da Computação

Trabalho de Graduação

Integração de objetos da linguagem

C# com regras de produção

Recife, Junho de 2008.

niversidade Federal de PernambucoGraduação em Ciência da Computação

Trabalho de Graduação

Integração de objetos da linguagem

C# com regras de produção

Recife, Junho de 2008.

niversidade Federal de Pernambuco Graduação em Ciência da Computação

Integração de objetos da linguagem

C# com regras de produção

Integração de objetos da linguagem

C# com regras de produção

Integração de objetos da linguagem

C# com regras de produção

[email protected]@cin.ufpe.br

Integração de objetos da linguagem

C# com regras de produção

[email protected]@cin.ufpe.br

Integração de objetos da linguagem

C# com regras de produção

[email protected]@cin.ufpe.br

Integração de objetos da linguagem

[email protected]@cin.ufpe.br

Integração de objetos da linguagem

[email protected]@cin.ufpe.br

Integração de objetos da linguagem

[email protected]@cin.ufpe.br

[email protected]

2

“Any sufficiently advanced technology

is indistinguishable from magic.” Arthur C. Clarke

"A atmosfera estava tão úmida que os peixes poderiam entrar pelas portas e sair pelas janelas,

navegando no ar dos aposentos." Regra escrita por

Gabriel G. Marquez,

compilável em R#

3

Agradecimentos

À minha família, especialmente meus pais, que me deram o apoio necessário durante esta e outras jornadas da minha vida. Aos amigos da Commit Solutions, assim como ao dissidente João Augusto e à de outro departamento Thaíza: se vocês não perguntassem tanto como andava meu TG, eu não teria me dedicado tanto pra deslanchar com esse trabalho aqui. Valeu! Ao meu orientador Geber, e também ao meu provável avaliador André Santos, que me ajudou no início deste trabalho com seus conhecimentos em .NET. Também a Pablo, que me disponibilizou o material do seu trabalho e também sua atenção.

4

Resumo

Soluções tecnológicas freqüentemente precisam automatizar processos cuja

linha de raciocínio utiliza conhecimento implícito. Este conhecimento pode ser

representado na forma de regras, que em conjunto formam uma base de

conhecimento capaz de inferir do mundo informações de relevância e tomar

determinadas ações quando um conjunto de premissas é satisfeito.

O presente trabalho apresenta uma proposta de linguagem para desenvolver

conhecimento baseados em regras como uma extensão simbiótica para C#, uma das

linguagens mais relevantes dentro do paradigma orientado a objetos.

Palavras-chave: C#, .NET, regras, integração, orientação a objetos, EOOPS.

5

Sumário

1. Introdução .................................................................................................... 7

2. Contextualização .......................................................................................... 8

2.1 RBS: Sistemas baseados em regras ................................................................ 8

2.2 Integração entre regras e linguagens orientadas a objetos ........................... 9 2.2.1 Compatibilidade Mútua ......................................................................................... 9 2.2.2 Encapsulamento ................................................................................................... 10 2.2.3 Herança ................................................................................................................ 11

2.3 Avaliação de trabalhos relacionados ........................................................... 12 2.3.1 C# .......................................................................................................................... 12 2.3.2 JEOPS .................................................................................................................... 15 2.3.3 NéOPUS ................................................................................................................ 19 2.3.4 R++ ........................................................................................................................ 20

2.4 Resultado da análise .................................................................................... 24

3. R# ................................................................................................................ 27

3.1 Linguagem R# .............................................................................................. 28 3.1.1 Rulebase ............................................................................................................... 28 3.1.2 Ativar ou desativar regras e rulebases ................................................................. 31 3.1.3 Choose .................................................................................................................. 33 3.1.4 Declaração ............................................................................................................ 34 3.1.5 Outras considerações sobre o uso da linguagem ................................................. 35

3.2 Mecanismo de inferência de regras ............................................................. 36

4. Implementação de R#: Proof-of-Concept ................................................... 41

5. Conclusões .................................................................................................. 45

6. Referências ................................................................................................. 46

Assinaturas ......................................................................................................... 47

6

Índice de Figuras

Imagem 1 .............................................................................................................. 8

Imagem 2 ............................................................................................................ 13

Imagem 3 ............................................................................................................ 13

Imagem 4 ............................................................................................................ 13

Imagem 5 ............................................................................................................ 14

Imagem 6 ............................................................................................................ 14

Imagem 7 ............................................................................................................ 14

Imagem 8 ............................................................................................................ 16

Imagem 9 ............................................................................................................ 17

Imagem 10 .......................................................................................................... 18

Imagem 11 .......................................................................................................... 19

Imagem 12 .......................................................................................................... 21

Imagem 13 .......................................................................................................... 27

Imagem 14 .......................................................................................................... 28

Imagem 15 .......................................................................................................... 29

Imagem 16 .......................................................................................................... 31

Imagem 17 .......................................................................................................... 33

Imagem 18 .......................................................................................................... 35

Imagem 19 .......................................................................................................... 36

Imagem 20 .......................................................................................................... 38

Imagem 21 .......................................................................................................... 39

Imagem 22 .......................................................................................................... 40

Imagem 23 .......................................................................................................... 41

Imagem 24 .......................................................................................................... 42

Imagem 25 .......................................................................................................... 42

Imagem 26 .......................................................................................................... 43

Imagem 27 .......................................................................................................... 44

7

1. Introdução

Soluções tecnológicas freqüentemente precisam automatizar processos cuja

linha de raciocínio utiliza conhecimento implícito. Enquanto a forma explícita de

conhecimento pode encontrar expressividade eficaz em mecanismos formais

amplamente disponíveis, aspectos do conhecimento tácito podem continuar implícitos

quando há um uso puramente funcional dessas representações [1]. Este conhecimento

encontra uma representação mais eficaz na forma de regras, que em conjunto são

capazes de construir uma base de conhecimento que tenha o poder de inferir do

mundo percebido pela aplicação informações de relevância e tomar determinadas

ações quando um conjunto de premissas é satisfeito.

Existem abordagens de sistemas de conhecimento baseados em regras para

praticamente todas as linguagens do mercado que seguem o paradigma de

programação orientado a objetos, que desde os anos 90 é o paradigma dominante na

indústria de software e o que mais cresce em uso [2]. A relativa abrangência de tais

sistemas mostra que há interesse da comunidade tecnológica na área, no entanto nem

sempre a combinação de regras e objetos satisfaz na tentativa de promover um

sistema híbrido integrado.

Há uma dificuldade em suportar ambos os paradigmas de forma harmônica. Os

dois são adequados para diferentes tipos de tarefas e uma integração satisfatória

precisa resolver algumas questões conceituais a respeito desses conflitos de

paradigma.

Faz parte da solução para integrar de maneira bem sucedida ambos os

paradigmas encontrar a simbiose lingüística [3] entre as duas linguagens, isto é,

permitir que os programas escritos em uma linguagem possam integrar-se a

programas implementados na outra de forma transparente e automática. A maior

justificativa para haver esforço na direção da simbiose lingüística está na necessidade

do usuário especialista que definirá tais regras de vivenciar uma experiência de

especificação sem se envolver com particularidades de um ambiente de codificação

para desenvolvedores de software, apenas preocupado com a definição do domínio o

qual ele reúne conhecimento implícito valioso.

O presente trabalho apresenta a proposta de criação de um novo sistema de

produção de regras, e descreve entre os vários componentes da solução uma

linguagem criada para especificar conhecimento baseados em regras como um

mecanismo simbiótico com C#, uma das linguagens mais relevantes dentro do

paradigma orientado a objetos.

8

2. Contextualização

Esta seção é destinada ao entendimento geral do problema, o que inclui um

esclarecimento breve sobre termos e particularidades importantes do domínio da

solução descrita neste trabalho, realizado nas seções 2.1 e 2.2, assim como inclui

também a avaliação de trabalhos relacionados, um dos principais critérios utilizados na

concepção da solução, descrita na seção 2.3.

2.1 RBS: Sistemas baseados em regras

Sistemas baseados em regras – também chamados de Sistemas de produção ou

RBS, Rule Based Systems – são aplicações de inteligência artificial desenvolvidos a

partir de elementos que, juntos, representam um conjunto de conhecimentos

heurísticos em relação a um domínio. Esses elementos são chamados de regras de

produção. Cada regra de produção descreve, dada uma condição do ambiente

avaliado, como o sistema deverá se comportar. Por permitir um modo diferenciado de

codificação de conhecimento, tornou-se um método bastante utilizado para a

definição de sistemas especialistas [4] e agentes inteligentes [5].

Uma regra de produção é composta por uma condição e um conjunto de ações a

serem realizadas caso a condição seja satisfeita. À condição também se denomina

antecedente, parte if ou left-hand-side, enquanto o conjunto de ações é

freqüentemente chamado de conseqüente, parte then ou right-hand-side. A Imagem 1

ilustra o conceito de uma regra.

Imagem 1

Memória de trabalho é um conceito bastante necessário na compreensão e

desenvolvimento (freqüentemente também no uso) de um sistema de produção. Ela

constitui um espaço alocado pelo sistema que contém os fatos, ou seja, o que é

considerado verdade sobre o ambiente. Os fatos serão avaliados em relação as

condições descritas nas regras de produção. Eles podem ser afetados pelas ações

9

previstas nas regras cujas condições sejam satisfeitas, ou seja, um fato pode ser

removido, modificado ou adicionado.

Um sistema de produção, em linhas gerais de funcionamento, contém regras e

uma memória de trabalho. Durante sua execução, confronta as regras e os fatos: as

regras que possuem condições verdadeiras em relação aos fatos apresentados são

selecionadas. Se houver apenas uma regra selecionada, suas ações são realizadas. Se

houver mais de uma, como seus distintos conjuntos de ações podem configurar um

paradoxo, há o procedimento de seleção da regra prioritária, uma resolução para este

tipo de conflito. O ciclo de execução de um sistema de produção se resume a estes

passos, que podem ser denominados de Unificação – o confrontamento dos fatos com

as condições das regras – Resolução de Conflitos e Disparo da regra selecionada.

Enquanto houver regra selecionada na etapa de unificação, o ciclo continua.

2.2 Integração entre regras e linguagens orientadas a objetos

Entre os paradigmas de regras e de objetos, não deve-se estabelecer somente

um cenário de escolha. Ambos os paradigmas são adequados para diferentes tipos de

tarefas em programação e, em razão desta complementaridade, a integração entre os

paradigmas pode ser vista como uma necessidade evolutiva. Programadores

orientados a objeto precisam algumas vezes de formas mais abertas de controle para

representação de conhecimento, por vezes também necessitam de mecanismos não-

determinísticos. Programadores de bases de regras necessitam realizar representações

mais sofisticadas dos fatos do mundo sobre os quais as regras atuam. Implementações

estranhas em um paradigma podem encontrar no outro uma forma mais apropriada,

como regras difíceis de manter ou inadequadas que podem ser substituídas por um

método ou por um novo módulo de classes.

Algumas dimensões qualitativas desta integração, no entanto, devem ser

consideradas, de modo a garantir que, do ponto de vista conceitual dos paradigmas a

serem integrados, não houve conflitos ou concessões que prejudiquem o andamento

das atividades de programação pelo uso do resultado obtido. O restante desta seção

trata de avaliar algumas destas dimensões.

2.2.1 Compatibilidade Mútua

Avaliar o grau de compatibilidade mútua entre as linguagens é crucial, pois uma

das partes pode perder sua força natural quando utilizada através dos mecanismos de

integração. Conseguir esta compatibilidade entre regras e objetos não se mostra um

esforço trivial de ser realizado sem concessões. Um bom exemplo desta dificuldade

10

está no fato de que as linguagens que descrevem regras historicamente se basearam

na possibilidade de ter acesso à estrutura dos fatos contidos nos elementos da

memória de trabalho. De acordo com a filosofia orientada a objetos, esta atitude é

vista como totalmente contrária a seus princípios, sua tendência é de esconder a

estrutura de dados no interior dos objetos.

Outro ponto corroborador com o aumento desta complexidade de conseguir

um bom grau de compatibilidade mútua entre os paradigmas de regras e objetos é a

integração disponibilizar todas as funcionalidades sintáticas da linguagem de objetos

para a linguagem de regras, como variáveis locais e expressões diferenciadas, esta

última sendo atualmente uma atitude bastante comum de linguagens de alto nível. A

linguagem C#, que na sua versão 3.0 disponibiliza, além dos tradicionais constructos

como “if”, “while”, “for”, outros mais elaborados como “foreach” e “using”, também

dá suporte ao uso de lambda-expressions e é inerentemente integrada com uma

linguagem avançada de consultas a dados chamada LINQ.

2.2.2 Encapsulamento

O tratamento da questão do encapsulamento é uma dimensão importante a

ser considerada na integração das linguagens de regras e objetos. Apesar de ter

origens relacionadas com o grau de compatibilidade mútua, como qualquer dimensão

em última instância teria, vale a pena considerar separadamente a situação do

encapsulamento em razão dos problemas gerados por ela serem de natureza mais

específica, por este motivo demanda um esclarecimento mais dedicado.

Um dos pontos principais a respeito do encapsulamento trata do conflito entre

as filosofias: objetos são projetados a princípio para esconder a estrutura de dados

nele contida, assim como detalhes de implementação; regras são produzidas, à parte

da integração com a filosofia orientada a objetos, voltadas para a manipulação

explícita da estrutura de dados. Unir as duas perspectivas resulta em contradição neste

ponto, e partindo do princípio que um programador de base de regras não deveria

referenciar na sua parte do código detalhes de implementação e utilizar a estrutura de

dados explicitamente, este conflito, neste trabalho, será tratado com uma concessão

do paradigma de regras em relação a estes fatores. Embora haja a concessão para

resolver o conflito, pelo fato desta ser tomada em favor de uma boa prática de

engenharia de software ela pode ser vista não tão somente como uma concessão, mas

como uma melhoria no paradigma de regras.

Outro fator relacionado a encapsulamento a ser ponderado na elaboração ou

avaliação de um mecanismo de integração entre regras e objetos reside na

consideração de que modificações são necessárias para permitir a uma aplicação o

11

acoplamento de uma base de regras. Um exemplo de como este fator é relevante:

imagine que, para uma classe de objetos poder ter conhecimento sobre si

representado em uma base de regras, precise herdar de uma certa superclasse

pertencente ao motor de inferência da base de regras. Essa necessidade resulta num

acoplamento muito forte, e pode impor restrições prejudiciais.

Quando os sistemas de inferência de regras ou a própria linguagem impõem

restrições nos objetos da aplicação, de modo a interferir fortemente a ponto de violar

a arquitetura de uma solução orientada a objetos, esta integração não ocorre da forma

mais adequada. Esta afirmação pode sugerir que o sistema de regras adequado em

relação a este fator não causa interferências na solução orientada a objetos, não

precisa modificar a aplicação. Alguns pontos de acoplamento sempre serão

necessários, a partir do código orientado a objetos, o que implica numa modificação da

aplicação. Como não é possível associar de forma totalmente desacoplada os dois

paradigmas, pode ser entendido que este fator de qualidade é impossível de ser

alcançado. No entanto, trata-se de evitar interferência que causa violação arquitetural

ou impede algumas atividades na programação orientada a objetos. É possível realizar

uma associação de qualidade utilizando o mínimo de acoplamento necessário,

escolhendo as alternativas menos invasivas e, quando possível, realizando

automaticamente a maior parte das alterações requeridas.

2.2.3 Herança

A maneira como regras enxergam a herança entre classes é um aspecto muito

importante da integração, devido a ser uma característica inerente da orientação a

objetos que não deve ser desconsiderada. Durante a definição de uma linguagem de

regras orientada a objetos, a questão da herança é encontrada em várias decisões:

a) Quando se decide de que maneira variáveis são tipadas nas regras;

b) Se há um quantificador universal (por exemplo, uma regra contendo: “para

qualquer objeto x, verifique essas condições”);

c) Se ao definir uma regra para uma classe, ela será aplicada a objetos

pertencentes a subclasses desta;

d) Se a integração implica na necessidade da classe especificada herdar de

alguma classe do motor de inferência, este é um problema de violação de

encapsulamento, como já citado, mas também de herança, pois impede

que a prática seja utilizada nas classes que estão sob a regulamentação de

uma base de regras.

12

2.3 Avaliação de trabalhos relacionados

Esta seção seleciona a análise de trabalhos relacionados, resultando na

avaliação de C#, linguagem hospedeira do R#, e de algumas soluções para

especificação e inferência de regras. Existe seguramente mais de uma dezena de

projetos interessantes na área, no entanto foi dado foco de análise nos sistemas que

mais apresentassem pontos críticos e de confluência para a criação deste trabalho.

2.3.1 C#

A seção anterior tratou das questões que envolvem a integração de um

mecanismo de regras em uma linguagem orientada a objetos. Ao passo que muitas das

considerações levantadas colocam o suporte à filosofia e às funcionalidades da

linguagem hospedeira como principal fator de adequação na integração, esta seção foi

criada com o objetivo de avaliar C# principalmente em relação a esses aspectos.

Em junho de 2000, a Microsoft anunciou tanto a plataforma .NET quanto C#,

uma nova linguagem de programação. C# é uma linguagem orientada a objetos de

propósito geral fortemente tipada, concebida tendo como principal objetivo “combinar

de forma ótima simplicidade, expressividade e performance” [6].

A sintaxe de C# baseia-se bastante em C++ e também é influenciada por

aspectos de outras linguagens, como Delphi e Java. A plataforma .NET tem como

núcleo uma Common Language Runtime, máquina virtual para aplicativos escritos em

uma linguagem intermediária denominada IL. O código escrito na linguagem C#, assim

como em outras linguagens suportadas pela plataforma .NET é compilado para IL e

pode ser executado em qualquer máquina que tenha a CLR da plataforma .NET.

As linguagens orientadas a objetos mais recentes provêem uma série de

funcionalidades de linguagem de alto nível, que permitem o desenvolvedor executar

atividades de maneira mais eficiente, sem ferir completamente o paradigma

hospedeiro, no caso, o de orientação a objetos. Uma linguagem como C# dá acesso ao

uso de lambda-expressions, extension methods, tipos anônimos, entre outras

inovações dentro da linguagem, que não possuem necessariamente aderência ao

paradigma orientado a objetos. As linguagens dominantes do mercado precisam, a

cada nova versão, aumentar suas possibilidades de expressão e, para isso, podem

realizar uma integração de itens que a princípio seriam considerados estranhos ao

paradigma, mas há um esforço focado na adequação, e quando há uma incorporação

destes itens de maneira que sua inserção não é causadora de não-conformidades na

13

especificação da linguagem e no seu uso e sua integração é bem-sucedida nas

dimensões qualitativas.

Assim sendo, a seguir será listada uma seleção das funcionalidades mais

inovadoras da linguagem C#, considerando como critério de seleção o diferencial das

funcionalidades e o potencial de conflito de integração destas funcionalidades com o

mecanismo de regras. O critério de seleção ajuda porventura na elicitação de uma

impossibilidade completa de integração de alguma funcionalidade, como também

pode apontar para uma combinação satisfatória com o mecanismo de regras.

Delegate: é um tipo que referencia métodos, encapsulando o acesso a métodos

anônimos ou nomeados, mas são type-safe. A linguagem apóia-se em delegates para a

manipulação de eventos.

Imagem 2

Object Initializer: é possível inicializar um objeto substituindo o construtor tradicional

por uma inicialização apenas das propriedades que o desenvolvedor quiser

inicialmente. Ideal para evitar a construção de muitos construtores.

Imagem 3

Type Inference: é possível inicializar uma variável sem declarar a priori o seu tipo,

contudo em tempo de compilação seu tipo é descoberto.

Imagem 4

14

Anonymous Type: permite criar tipos implicitamente em código. Uma vez que eles não

tem um nome, são guardados através da palavra-chave var, o que pede ao compilador

C# que use Type Inference para checar o tipo da variável.

Imagem 5

Extension Methods: disponível a partir da versão 3.0 da linguagem, permite

“acrescentar” métodos a uma classe sem criar um tipo derivado ou recompilar esta

classe, tampouco modificar a própria classe. Na sua declaração, um extension method

é um método estático, que recebe no primeiro parâmetro a palavra-chave this.

Imagem 6

Lambda Expressions: é uma função anônima que pode conter expressões e comandos,

podendo ser utilizada para a criação de delegates, por exemplo. Toda lambda

expression utiliza o operador =>, onde o lado à esquerda do operador representa a

entrada e o lado à direita do operador representa o retorno da função.

Imagem 7

Realizando um balanço geral das funcionalidades apresentadas, extension

methods é uma funcionalidade que em nada interfere na integração com regras. A nova maneira de inicializar objetos sem o uso de um construtor explícito acrescenta uma complexidade ao compilador da integração apenas, mas não impede a integração. O uso de delegates, por conseguinte de lambda expressions, ao contrário do que possa parecer, pode facilitar o trabalho de especificação das regras e sua posterior compilação, de qualquer forma impactando na complexidade do compilador. O uso de tipos anônimos pode não ser considerado um grande problema se levarmos em conta que tipos anônimos não podem ser regulamentados por uma base de regras.

15

2.3.2 JEOPS

O JEOPS (Java Embedded Object Production System) é um motor de inferência

para regras integrado à Java. Teve seu lançamento no ano de 1997, fazendo em 2000

parte da dissertação de mestrado do Centro de Informática da Universidade Federal de

Pernambuco pelo então aluno de pós-graduação Carlos Santos da Figueira Filho,

orientado pelo Prof. Dr. Geber Lisboa Ramalho [7]. No ano de 2006, o JEOPS recebeu

uma versão equivalente de sua estrutura integrado à linguagem C++, que foi batizado

de CEOPS++, elaborado por Pablo de Santana Barbosa em seu trabalho de conclusão

de curso de graduação [8].

O presente trabalho compartilha com os projetos JEOPS e CEOPS++ não só o

professor orientador e a instituição, mas também a proposta de complementaridade

lingüística para o paradigma de orientação a objetos, no caso para a plataforma .NET,

especificamente a linguagem C#. Java e C# guardam entre si muitas semelhanças,

entre elas muitos pontos de confluência a respeito de suas filosofias.

A idéia norteadora da concepção do JEOPS foi de “criar um sistema que

primasse sobretudo pela facilidade de uso e pelo respeito às características do

paradigma orientado a objetos, o que seria conseguido com a maior integração

possível entre as regras e a linguagem hospedeira” [7]. Performance também foi um

aspecto considerado, dado o objetivo de utilizar o JEOPS em aplicações reais,

sobretudo para a implementação de agentes, freqüentemente utilizados em

aplicações que necessitam de um tempo curto de resposta.

Da idéia inicial surgiu, como princípio primeiro e mais importante, o que foi

chamado por Carlos Figueira de “uniformidade da integração”, isto é, a sintaxe das

regras em JEOPS buscaria semelhança máxima à sintaxe da linguagem hospedeira,

Java. Como o atingimento de uniformidade na integração implica na consideração de

vários outros fatores (alguns destacados neste relatório, na seção 2.2), houve também

a preocupação no desenvolvimento da linguagem JEOPS de suportar todos os

elementos de sintaxe da linguagem Java, como por exemplo, permitir chamadas

aninhadas de métodos e acessar atributos públicos. Contudo, dado que o JEOPS

trabalha baseado na versão 1.4 de Java, vários elementos da sintaxe atual certamente

não são suportados, o que causa uma incompatibilidade entre JEOPS e as novas

funcionalidades de linguagem providas pelas versões recentes de Java.

16

Imagem 8

As regras JEOPS são armazenadas em um arquivo que representa uma base de

regras, este arquivo pode conter várias regras, assim como métodos e declarações de

variáveis. O diagrama na Imagem 8, baseado num exemplo encontrado na dissertação

de Carlos Figueira [7] ilustra uma base de regras Familia, que contém uma regra

apenas, chamada encontraAncestrais, esta descrevendo conhecimento a respeito de

objetos das classes em Java Pessoa e Objetivo. As classes Java, na parte superior do

diagrama, estão descritas de acordo com a modelagem UML, enquanto o código

correspondente à especificação da regra foi colocado na íntegra, na parte inferior do

diagrama. As cores atribuídas às palavras-chave da linguagem não necessariamente

correspondem à realidade do JEOPS, foram assim atribuídas neste diagrama para

possibilitar uma melhor leitura do código explicativo.

17

O processo de compilação de uma base de regras no JEOPS funciona de acordo

com a Imagem 9. Inicialmente, o JEOPS realiza uma compilação dos arquivos que

contém bases de regras (no exemplo, Familia.rules) e os transforma num arquivo Java

(Familia.java). Em seguida, o compilador de Java gera os arquivos .class que serão

interpretados pela máquina virtual Java (JVM). O arquivo Java compilado pelo JEOPS

gera dois arquivos .class, pois dentro dele estavam definidas duas classes: uma

representa a base de conhecimentos (Familia.class) e a outra representa a base interna

de regras (Jeops_RuleBase_Familia.class). O acesso à base interna de regras não

precisa ser efetuado pelo usuário do JEOPS. É automaticamente gerado pelo seu

compilador e serve apenas para o próprio mecanismo de inferência da ferramenta.

Imagem 9

A arquitetura do JEOPS (em alto nível) funciona de acordo com o diagrama

exibido na Imagem 10. O agente comunica-se com o JEOPS através de uma fachada.

Internamente, o JEOPS contém: uma base de objetos, onde são armazenadas as

referências para os objetos que fazem parte da memória de trabalho; uma base

interna de regras, que contém as regras e é responsável por responder à rede RETE se

as condições de uma regra são verdadeiras; a rede RETE [11], que é o algoritmo de

casamento de padrões que identificará, a partir de fatos modificados, possíveis

conjuntos de objetos que, de acordo com algumas regras, devem ser enviados para o

conjunto de conflitos e, por fim, o conjunto de conflitos, onde são armazenados os

pares regra-objetos verificados na base de conhecimentos através do RETE e do acesso

à base interna de regras. O conjunto de conflitos ficou separado do mecanismo do

motor para permitir uma maior modularidade e extensibilidade em relação a como o

usuário gostaria de resolver os conflitos. Além de um conjunto de estratégias de

resolução de conflitos providas pelo JEOPS que podem ser escolhidas pelo usuário,

também é fornecida uma interface para o usuário especificar uma estratégia

diferenciada, caso seja necessário.

18

Imagem 10

O projeto do JEOPS considerou aspectos de simbiose lingüística, propôs

estratégias que levam em consideração uma integração das tecnologias em um nível

lingüístico satisfatório. Atingiu em grande parte seus objetivos nessa área, pois foi

criada uma linguagem de regras complementar a Java que compartilha a mesma

filosofia de codificação, o que permite que desenvolvedores com experiência em Java

desenvolvam regras em JEOPS conservando a simbologia da linguagem hospedeira e

mantendo vantagens do paradigma orientado a objetos. No entanto, o motor não

detecta automaticamente quando um novo fato existe na base de conhecimento,

deixando a cargo do desenvolvedor avisar quando há esta modificação. Isso resulta

numa fraca transparência na integração entre as duas linguagens, o que é ruim do

ponto de vista do acoplamento, podendo implicar inclusive em problemas de

modularidade entre o código da aplicação e a manipulação do mecanismo de regras.

Como crítica principal ao trabalho, mesmo que o JEOPS tenha como principal

objetivo sanar este problema, percebe-se a necessidade de resolver problemas de

integração entre regras e objetos, pois além de não haver uma simbiose lingüística

completa, existem outras abordagens de integração de regras (explicadas nos

próximos trabalhos) que são mais aderentes ao paradigma orientado a objetos.

A disponibilização da atividade de especificação de regras deve ser realizada de

tal forma que o período de aprendizado para o uso da linguagem seja curto. O esforço

que uma linguagem demanda para ser aprendida e proveitosamente assimilada tem

grande impacto no uso que os profissionais farão da linguagem, portanto, no resultado

qualitativo da solução resultante. No tocante ao aprendizado, o JEOPS foi muito

satisfatório, por ter sua sintaxe bastante similar a de Java.

19

2.3.3 NéOPUS

O NéOPUS é a proposta de integração entre regras e objetos para SmallTalk,

realizado por François Pachet em 1992. Baseado no sistema OPUS [9], foi pioneiro na

consideração de fatores qualitativos na integração de um mecanismo de regras a uma

linguagem orientada a objetos, obtendo sucesso na busca de suavizar a transição entre

a linguagem hospedeira e as regras. Sua existência provocou uma evolução na maneira

de se enxergar a integração entre os paradigmas, e pode ser considerada como

precursora direta do JEOPS, que foi bastante influenciado por esta solução [7].

Por prezar sobretudo por valores como transparência e manutenção total das

funcionalidades da linguagem hospedeira Smalltalk, pode ser vista como um grande

avanço e como uma estratégia que investiu adequadamente na compatibilidade

mútua. A Imagem 11 contém a implementação em NéOPUS da mesma regra mostrada

na seção destinada ao JEOPS. Assim como o JEOPS, a especificação de regras em

NéOPUS faz uso de objetos respeitando o encapsulamento.

Imagem 11

O maior problema no uso do NéOPUS está na linguagem hospedeira. Smalltalk

há algum tempo deixou de ser uma linguagem utilizada amplamente, fato que

distancia a solução de integração do público utilizador. Soluções como o

JEOPS/CEOPS++ estão, neste sentido, mais próximas do estado da arte e do conjunto

de linguagens dominantes na atualidade, sendo entre elas também pertencente C#.

Foi selecionada para esta análise por ter um papel importante como precursora do

JEOPS e de grande parte das soluções de integração entre regras e objetos.

20

2.3.4 R++

A linguagem R++ nasceu no Bell Labs em 1994. Sua idéia era construir uma

extensão da linguagem C++ adicionando o paradigma de regras. No entanto, essa

extensão foi feita de forma diferente das realizadas até então. Ao invés de ter motores

de inferência que se baseavam no casamento de padrões (como NéOPUS, JEOPS)

realizados por algoritmos como o RETE para tratar que regras deveriam ser verificadas,

R++ propôs uma alternativa radical: as regras são traduzidas na linguagem hospedeira

e não há nenhum motor de inferência central sendo executado. Para a especificação

de regras, R++ modificou a linguagem C++ simplesmente acrescentando um novo

constructo: a regra, vista como um membro de uma classe.

Não eram, todavia, regras tradicionais de produção como as outras. R++

baseou-se em um tipo de regras diferenciado, denominado path-based rules [10]. Este

tipo de regra surgiu em 1989 em uma pesquisa conduzida por James Crawford e

Benjamin Kuipers, na University of Texas, em Austin, que gerou uma linguagem

precursora de R++ chamada Algernon. Embora haja espaço de argumentação que as

linguagens tradicionais de regras teriam uma expressividade maior para tratar

conhecimento, esta decisão foi justificada por menores interferências do paradigma de

regras path-based com o paradigma orientado a objetos, e pelo ganho considerável de

performance observado. As regras path-based provaram, de qualquer modo, serem

um modelo viável para integração entre regras e objetos.

As regras em R++ possuem sintaxe e semântica similar a member functions em

C++, conforme pode ser visualizado na Imagem 12. São declaradas dentro das classes,

operam diretamente nos objetos C++, respeitam controladores de acesso (public /

protected / private), são membros herdados por subclasses, e possuem acesso à

instância ‘this’, assim como métodos ou funções em C++. Ao contrário de funções, as

regras são disparadas automaticamente ao invés de chamadas.

São denominadas path-based rules porque seguem estritamente um caminho

de ponteiros entre objetos, a partir de ‘this’ para objetos relacionados. Desta forma, é

dado aos objetos apenas a capacidade de acesso que eles possuem inerentemente, de

tal forma que eles não podem suplantar os relacionamentos especificados na

arquitetura orientada a objetos, como numa regra tradicional que poderia relacionar

dois objetos que não têm nenhuma relação pré-estabelecida entre si, ou

recursivamente entre suas relações.

O estudo responsável pela concepção de R++ afirma que o fato de “outras

linguagens de regras poderem realizar relacionamentos arbitrários entre objetos não

relacionados” [12] viola o princípio da localidade de referência projetado num modelo

orientado a objetos, e que isso torna uma base de regras mais complexa, por ser mais

21

difícil de prever e entender seus caminhos. Principalmente por este motivo, R++

consegue uma integração com maior conformidade em relação ao paradigma

orientado a objetos que as linguagens de regras de produção tradicionais.

Imagem 12

Esse tipo de regra é chamado de path-based, uma tradução literal levaria a um

“orientado a caminhos”, pois as referências checáveis em suas condições atravessam

caminhos entre objetos a partir de um objeto raiz (a instância ‘this’). No entanto o

título dado a este tipo de regra parece ter sido atenuado por receio de soar pouco

modesto em relação à aderência obtida com sua proposta de integração. Um nome

mais adequado, porém talvez mais ambicioso, seria regras “orientadas a objetos”, uma

vez que é exatamente nisso que a regra se transforma, como um elemento que, de tão

orientado a objetos, tornou-se um membro de classe e respeita, em R++, todo o

paradigma orientado a objetos.

A maneira que R++ dispara regras está relacionada ao monitoramento das

chamadas a métodos que modificam o estado do objeto e dos construtores. As regras

são controladas por mecanismos naturais do paradigma orientado a objetos. Quando

uma instância de uma classe é criada, o objeto correspondente é verificado pelas

regras definidas para aquela classe. O único modo de desconsiderar regras em um

objeto é destruí-lo, pois uma regra está sempre ativa em todos os objetos de uma

classe, incluindo objetos de classes derivadas.

22

Sobre a decisão de tornar as regras membros de classe, R++ enxerga esta

associação uma maneira natural de integrar regras com os outros elementos do

paradigma orientado a objetos, colocando regras para trabalhar diretamente com

objetos que são instâncias da classe. Assim como atributos e métodos de acesso a

dados definem a representação de uma classe e funções e métodos definem a

interface de uma classe, regras definem o comportamento automático de uma classe.

Outras considerações sobre como R++ trata regras são:

1. Uma vez que regras são membros de classe, então são definidos pelos

desenvolvedores da classe, não por usuários dela. O fato de descrever

comportamento de um conjunto de objetos do mesmo tipo, ainda que seja

na forma de regras, quando esta especificação é realizada além dos limites

da classe que os descreve, é considerado segundo a visão desta solução

uma violação ao paradigma orientado a objetos.

2. Regras possuem acesso aos membros visíveis da sua classe imediata, ou

seja, todos os ali declarados, sejam public, protected ou private, assim como

os membros visíveis externamente de classes associadas.

Apesar de estar em conformidade com a decisão defendida pela

solução, esta característica desenha uma visão diferente da que considera

que uma regra deve especificar conhecimento sobre um objeto apenas pelo

seu comportamento externo [2].

No entanto, a tecnologia da linguagem C++ permite que esta

modularidade seja conseguida caso a aplicação a tenha como crítica. Em

tais aplicações, o projetista da classe pode encapsular a estrutura de dados

e definir, através de uma interface pública, as regras do objeto.

3. Regras definidas em uma classe que é herdada também são herdadas, isto

é, também funcionam em instâncias de classes derivadas.

4. Uma regra em uma classe derivada pode sobrescrever uma regra de mesmo

nome em uma classe básica.

5. Regras não podem ser chamadas, então não possuem parâmetros de

entrada, nem tipo de retorno, tampouco especificação de acesso public,

protected ou private.

Em relação à especificação de acesso à regra, não parece fazer

realmente muito sentido sua existência, uma vez que ela não pode ser

chamada. No entanto, permitir que regras (todas ou algumas em específico)

possam ser desativadas durante a execução da aplicação poderia ser uma

alternativa interessante para o desenvolvimento de algumas aplicações,

tendo em vista que regras em R++ estão sempre ativas em instâncias de

23

uma classe, e sua única maneira de impedir o comportamento automático

das regras é destruir o objeto. O uso de um modificador poderia indicar que

regras podem ter seu estado alterado apenas pela classe hospedeira

(private), também por todas as classes friend (protected), ou também por

classes derivadas (public), permitindo também uma quarta opção, onde a

regra não pode ter seu estado alterado (readonly).

Também falta um mecanismo de acesso para especificar que regras

podem ser sobrescritas (virtual), as que são definidas apenas em parte em

uma classe abstrata, isto é, não instanciável e as classes que herdarem

precisariam especificá-las (abstract), assim como as que nenhuma classe

derivada poderia sobrescrever (final).

Realizando uma análise de R++ a respeito do seu “motor de inferência”, na

solução, ao contrário das linguagens de regras tradicionais, não é centralizado e está

distribuído na implementação de cada objeto regulamentado. O algoritmo utilizado

para o processamento de regras path-based é mais simplificado que um algoritmo de

casamento de padrões, uma vez que regras em R++ simplesmente se transformam em

funções em C++ quando compiladas, e não é necessário um interpretador em tempo

de execução para realizar esta tarefa, tampouco um motor.

Para cada dado monitorado por uma regra, o compilador de R++ adiciona um

dado de controle específico que, durante a execução, referenciará os objetos raízes.

Quando uma mudança relevante é realizada em um dado de um objeto que não é raiz,

cada regra disparada seguirá as referências aos objetos raízes e então são avaliadas

suas condições em todos os caminhos que podem ser formados a partir daquela raiz e

que passam pelo objeto modificado.

O algoritmo utilizado para o raciocínio das regras path-based é geralmente mais

eficiente que o motor de inferência baseado em RETE. De acordo com um estudo

comparativo [10], no pior caso o custo em R++ é de O(rn2) enquanto numa

implementação RETE é de O(rnv), onde r = quantidade de regras, n = quantidade de

objetos, v = quantidade de variáveis. Embora o pior caso seja raramente atingido na

prática, outros resultados empíricos suportam a eficiência das regras path-based.

Um problema administrativo relacionado aos direitos comerciais da linguagem

R++ a impede de ser utilizada. A empresa Lucent possui a patente da tecnologia,

porém a AT&T é proprietária do software que realiza a tecnologia. R++ foi

desenvolvida pelo Bell Labs, no entanto quando a Bell foi dividida, a propriedade

intelectual de R++, desenvolvida nos laboratórios entre a AT&T e a Lucent, não teve

uma definição, pois as duas companhias disputaram a sua propriedade sem acordo.

24

2.4 Resultado da análise

Esta seção descreve, em linhas gerais, a reflexão tida como conseqüência da

análise dos trabalhos relacionados. As três soluções de integração entre regras não

representam o universo total de soluções em mecanismos de inferência, mas são bons

exemplares e criaram uma base suficiente para o presente trabalho. Mais

especificamente o JEOPS e o R++ têm gerado, no confrontamento de suas estratégias

bastante distintas, a principal fonte de decisões para a concepção da solução R#.

Ao verificar que a linguagem R# não teria dificuldades inviabilizadoras da

incorporação da sintaxe diferenciada de C# e que pretende ser coerente do ponto de

vista do paradigma de orientação a objetos, isso não é de todo suficiente para uma

simbiose lingüística adequada. A linguagem de regras também precisa se alinhar com a

visão de C#, sua filosofia, que encoraja a criação crescente de funcionalidades para

programação em alto nível. Desta forma, R# também deve, em tese, prezar pela

criação de constructos de linguagem inovadores que permitam uma expressividade

maior do especificador de regras quando em contato com R#.

A maneira com a qual C# visualiza simplicidade também deve ser considerada.

A linguagem disponibiliza constructos simples, mas que em conjunto, aninhados e

acessados um pelo outro podem levar a aplicação a um nível altíssimo de

complexidade. A complexidade deve ser permitida, pois algumas aplicações precisam

fazer uso de mecanismos mais sofisticados. Prezar cegamente pela simplicidade pode

tirar o poder de expressão e resolução de problemas de uma linguagem.

O restante desta seção foca na atividade de confrontar os paradigmas

levantados por JEOPS e R++, na direção de obter uma decisão de paradigma de regras

que permita uma uniformidade de integração com C#.

Regras path-based podem ser consideradas mais aderentes ao paradigma

orientado a objetos do que regras que promovem o casamento de padrões, no

entanto há uma questão aberta em relação ao poder de expressividade das regras. As

regras de casamento de padrão podem relacionar quaisquer objetos que estejam no

momento na base de objetos, enquanto uma regra path-based é direcionada a uma

instância e só pode relacionar o comportamento automático deste objeto a um outro

que ele possua, diretamente, uma relação.

Isto não é considerado, do ponto de vista das regras path-based, um problema,

uma vez que relacionar em uma regra arbitrariamente objetos que não têm uma

relação entre si não é uma atitude esperada de um programa doutrinado na boa

prática da engenharia de software com orientação a objetos.

O foco deste trabalho não concentra-se na implementação pura de um

mecanismo de regras para C#, mas na proposta de uma solução que consiga integrar

25

regras a C# da forma mais adequada possível do ponto de vista do paradigma no qual a

linguagem hospedeira se baseia. Há uma inclinação, portanto, para trabalhar com a

alternativa proposta pelo R++ e fazer uso no R# de regras path-based. C# possui

mecanismos que permitem realizar o equivalente ao que o compilador de R++ faz com

o código C++, transformando-o de modo a inserir as regras dentro das classes em C++.

Bibliotecas voltadas para instrumentação de código possibilitam este tipo de

implementação, e os mecanismos de reflexão são inclusive mais sofisticados do que

em C++. A grande perda em não utilizar o paradigma do JEOPS está na expressividade

das regras, como já dito. Programadores de bases de regras acostumados com o

paradigma de regras tradicional podem ter dificuldades ao tentar especificar certos

tipos de conhecimento, inclusive alguns que, declarados como na forma tradicional,

não trariam grandes violações ao paradigma.

Suponha que existem dois elementos em uma lista e é válido realizar uma

verificação entre eles. Eles não possuem, a priori, uma relação entre si, explícita, mas

estão na mesma coleção. Isto é, não há uma relação explícita necessariamente entre

eles, mas eles estão relacionados a um mesmo objeto, que possui essa coleção. E

devido à qualidade da relação dos dois elementos a este objeto que contém a coleção

ser a mesma – eles estão na mesma coleção, afinal –, eles são potencialmente

relacionáveis, por mais que eles não se relacionem, eles poderiam. O esforço de ter

essas relações mapeadas em ambos os objetos previamente, na etapa de arquitetura,

tem pouco valor para a orientação a objetos. Para regras, que tratam de conhecimento

de uma forma diferenciada de métodos, por exemplo, este tipo de relacionamento

potencial pode ser aproveitado e gerar um ganho na expressividade da linguagem.

O paradigma de pattern-matching, se aproveitado em situações específicas

como esta unido ao path-based, consegue ser complementar de forma coerente.

Obviamente, R# não deve permitir que a expressividade obtida permita a relação entre

objetos que são totalmente desacoplados um do outro, e também não apresentam

potenciais relações, através de relações em comum com outros objetos.

Em R++, as regras são um membro da classe a que se referenciam. O fato de

dizerem respeito ao comportamento de um objeto torna interessante essa decisão de

colocá-las como membro de classe, e parece inclinar para a afirmação que do contrário

há uma violação do encapsulamento. No entanto, para R# fazer o mesmo, precisaria

não somente trabalhar junto a um projeto C# para criar regras, mas tornar-se uma

extensão de C# que contivesse esse novo constructo: a regra. Fazer isso impediria

compatibilidade de R# com novas versões de C#, além de gerar um acoplamento muito

forte, de tal modo que um projeto C# que quisesse utilizar regras precisaria abdicar de

certas decisões para tornar-se um projeto R#.

26

O fato de colocar regras como membro de classe é também geradora da

seguinte situação indesejada: desenvolvedores de áreas diferentes, como regras e

código C# convencional, estariam utilizando o mesmo arquivo, o que abre margem

para uma série de inconvenientes de controle de versão, ainda que estas possuam

soluções que as resolvam com eficiência. O fato de eles estarem trabalhando em um

arquivo onde a totalidade do código deixa de ser de interesse mútuo de ambos abre

espaço para, sem problemas, optar-se pela maneira de especificação de regras do

JEOPS, isto é, fora de classes. Na própria linguagem C#, códigos de uma mesma classe

que possuem objetivos bastante distintos, como descrição visual dos objetos de uma

interface gráfica e o raciocínio lógico que os mantém são definidos em arquivos

separados. Dessa forma, pode tornar-se até aderente à filosofia de C# que bases de

regras, separadas dos objetos, os referenciem e especifiquem seu comportamento

automático. Assim como C# aceitou e fundamentou a partir da sua versão 3.0 o uso de

extension methods, que permitem a extensão do comportamento de um objeto

declarada fora de sua classe, as bases de regras fazem o mesmo.

A crítica realizada em R++ a respeito da criação de modificadores de acesso e

de sobrescrita são válidas e podem incrementar de maneira significativa o poder do

mecanismo de regras. Modificadores de permissão à sobrescrita de regras em classes

derivadas podem gerar melhoria no sentido de delimitar as formas que a herança em

reespecificação de regras pode ocorrer, podendo fixar um conhecimento perenemente

ou permitir que ele possa ser reespecificado. Também deve ser permitido que a

sobrescrita apenas acrescente uma pré-condição a mais, uma ação a mais, ou alguns

itens a mais. Suponha que existem duas classes Empregado e Gerente, onde Gerente

herda o comportamento de Empregado, e Empregado possui uma regra

FimExpediente, que verifica o horário e, caso tenha terminado o expediente vai

embora. O Gerente também possui esta regra, no entanto, antes de ir embora precisa

fazer mais algumas verificações. Em favor da prática do reuso, o Gerente não deve, ao

sobrescrever esta regra, repetir as partes que já estão especificadas na regra do

Empregado, mas apenas acrescentar suas verificações à regra base. Apesar de a

implantação de modificadores de acesso em regras parecer uma solução de pouco

valor prático, essa característica pode permitir o surgimento de arquiteturas bastante

sofisticadas em bases de conhecimento. Ativar ou desativar regras específicas de uma

base de conhecimento pode ser interessante em termos de refactoring, pois quando

várias regras possuem uma mesma condição, entre outras, talvez torne-se mais viável

ter uma regra que, possuidora desta condição, as ativa quando ela ocorre.

A seção a seguir descreve a proposta da solução R#, elicitada, principalmente, a

partir das reflexões realizadas durante a análise dos trabalhos relacionados.

27

3. R#

Esta seção apresenta a proposta da solução R#, que pretende ser um

mecanismo de regras integrado com a linguagem C# através de uma síntese cuidadosa,

de modo a otimizar o grau de compatibilidade mútua entre as linguagens, seguir a

filosofia da família .NET, especificamente da linguagem C#, respeitando as práticas e

princípios relacionados ao paradigma orientado a objetos.

A Imagem 13 ilustra uma regra R# que especifica comportamento automático

para a classe em C# Employee. A regra AskForSalaryIncrease está dentro da base de

regras EmployeeRules, que declara que todas as regras pertencentes a esta base diz

respeito a instâncias de Employee, ao fazer “EmployeeRules : Employee”. Cada base de

regras está contida em um arquivo, podendo uma rulebase conter várias regras.

Imagem 13

A regra AskForSalaryIncrease da Imagem 13 já está suficientemente bem definida a ponto de ser considerada uma regra compilável em R#. Uma regra é denominada pela palavra-chave ‘rule’ e possui um identificador, no caso AskForSalaryIncrease. Dentro das chaves por ela abertas encontram-se, basicamente, o left-hand-side ou lado if, que possui as condições que precisam estar verdadeiras para a regra ser disparada, e, após o divisor denotado pelo marcador ‘=>’ está o right-

hand-side ou lado then, que possui a ação ou o conjunto de ações a serem executadas caso a regra seja disparada.

28

Com um curto período necessário de aprendizado é possível compreender como descrever uma regra simples em R#. As funcionalidades apresentadas neste único exemplo já tornam possível especificar uma grande quantidade de regras. No entanto, R# tem uma proposta mais sofisticada que permite ao programador de bases de regras mais poder de especificação, além de possibilitar uma flexibilidade grande na arquitetura de regras a ser definida. Na seção a seguir, serão apresentados constructos de linguagem diferenciados de R# e situações nas quais eles podem ser utilizados.

3.1 Linguagem R#

Esta seção apresenta os elementos da linguagem R# de uma maneira mais

detalhada, de modo a cobrir suas funcionalidades e mostrar como ela resolve, no nível

lingüístico, os problemas que se propôs a solucionar.

3.1.1 Rulebase

Uma base de regras é definida num arquivo localizado dentro de um projeto R#

e que contenha o mesmo nome que a base de regras, acrescido da extensão “.rs”. Por exemplo, a base de regras EmployeeRules, que tem o objetivo de especificar as regras do objeto Employee, está localizada no arquivo “EmployeeRules.rs”, que por sua vez está dentro de um projeto R#.

A sintaxe básica para a definição de uma base de regras é: “rulebase

NomeDaBase { regras }”, entendendo-se por ‘regras’ a declaração de todas as que estão contidas na base de regras. Um arquivo de base de regras, por estar na plataforma .NET, precisa referenciar explicitamente o namespace das classes C# que utiliza, e isto é feito através da inclusão de elementos de sintaxe using no início do arquivo, antes de iniciar a declaração da base de regras. O exemplo A da Imagem 14 mostra a base de regras declarando usar o namespace BusinessCompanySolution, pois este contém a classe Employee, a qual a base de regras regulamenta.

Imagem 14

É possível dentro de uma base de regras referenciar outra base de regras, para a realização de algumas operações mais sofisticadas que veremos a seguir. Caso a outra base de regras esteja em outro projeto R#, a declaração da base de regras deve fazer de acordo com o exemplo B da Imagem 14 ao projeto R# “SyntheticAgentRules”.

29

Imagem 15

A Imagem 15 tem a intenção de mostrar como as rulebases podem ser

especificadas de modo flexível, pois dependendo da organização das regras que o

projetista achar mais conveniente, podem ser realizadas especificações distintas.

Se uma base de regras faz uma declaração como no item A, descrevendo após o

identificador da rulebase o “: Employee”, na assinatura da rulebase, obriga todas as

regras declaradas nela a regulamentarem a classe especificada pela rulebase, assim

como não as permite mais declarar na assinatura da regra esta relação. Se o projetista

das regras quiser dividir as bases de regras com relação às classes cujo comportamento

automático será especificado, o exemplo A é um modelo adequado de declaração das

bases de regras. A regra AskForSalaryIncrease, assim como todas as outras descritas

nesta rulebase necessariamente dizem respeito ao comportamento de Employee.

O caso do exemplo B mostra que quando uma rulebase não define previamente

que classe está sendo regulamentada, as regras devem fazê-lo. Sendo assim, regras

numa mesma rulebase podem regulamentar classes diferentes, desde que a base de

regras não defina que uma classe específica está sendo regulamentada através dela. O

exemplo B mostra-se mais adequado quando o projetista da solução de regras acha

melhor agrupar nas bases regras que não possuem necessariamente a mesma classe

regulamentada, porém possuem uma relação semântica que torna mais viável

aglutinar num mesmo arquivo regras relacionadas entre si. Também é uma boa opção

caso o projetista não queira amarrar à base de regras – que pode funcionar como um

mero agrupamento – a responsabilidade desta definição.

O exemplo C ilustra uma possibilidade na definição de bases de regras, que é

declarar uma rulebase abstrata, que serve apenas como uma interface de acesso a

regras, mas não as define. É adequada quando várias bases de regras são definidas

como no exemplo A e a solução necessita visualizar uma base de regras através de

uma outra organização. Como sugere o nome do exemplo C, esta rulebase abstrata

aglutina todas as regras, de várias bases de regras, relacionadas a salários. A palavra-

chave ‘interface’ escrita antes da palavra-chave ‘rulebase’ define uma base de regras

abstrata e em tal tipo de rulebase só podem constar referências a regras e não

30

declarações, como a referência vista no exemplo C, onde só há a assinatura da regra,

contendo antes do seu nome o nome de sua base de fato. No entanto, bases de regras,

mesmo que não sejam abstratas, podem referenciar regras de outras rulebases. R#

permite algumas operações com bases de regras, como já foi comentado, o que será

explicado em maiores detalhes na próxima seção e introduzido nos próximos

parágrafos. A criação deste tipo de base de regras é bastante útil para a concretização

destas operações.

Uma das preocupações com relação ao mecanismo de regras path-based em

R++ está no fato das regras estarem sempre funcionando, desde o momento da

criação do objeto, e nada, a não ser destruir o objeto, pode parar a avaliação

constante das regras em relação aos objetos. Foi observado em R++ a necessidade de

permitir que regras possam ser ativadas ou desativadas. Muitas aplicações podem ser

definidas sem este recurso, no entanto a possibilidade de ativar ou desativar regras,

todas ou algumas em específico, pode ser uma funcionalidade interessante tanto no

quesito expressividade quanto de um ponto de vista de arquitetura.

Permitir que uma regra ative uma outra (ou um conjunto de outras regras)

quando sua condição for satisfeita é habilitar a linguagem de regras para o reuso e o

refactoring, além de permitir uma melhor performance na inferência. Imagine que

dezenas de regras possuem, pelo motivo de haver uma relação semântica entre elas,

uma mesma condição em comum. Uma nova regra pode definir esta condição e,

quando esta condição for satisfeita, ativar as outras regras associadas para que elas

possam verificar suas condições diferentes.

Em R# é possível fazer esse tipo de operação e, pelo fato de uma base de regras

agrupar regras que têm uma relação em comum, é possível também ativar ou

desativar rulebases, o que significa, em última instância, efetuar essas modificações de

estado nas regras associadas à base de regras em questão.

A partir desta explicação, justifica-se a necessidade de bases de regras

abstratas, pois estas criam bases virtuais que referenciam regras de acordo com uma

relação definida pelo projetista, de tal modo que elas não são bases declaradoras de

regras, mas agrupadoras de regras que tem relações em comum mas estão definidas

em outras bases. Rulebases abstratas também habilitam aplicações futuras que

possam utilizar o R# a executarem estatísticas mais aprofundadas sobre o

comportamento dos objetos segundo as regras definidas.

Há uma maneira de declarar, tanto nas regras quanto nas rulebases, se elas

podem ou não serem ativadas ou desativadas. Visando guardar este recurso para

usuários avançados da linguagem, por padrão elas não podem, e caso o projetista

tenha o desejo de fazê-lo, deve colocar em uma linha acima da declaração da rulebase

(ou em uma linha acima da declaração de uma regra) o atributo ‘[Lockable]’, para

31

definir que esta base de regras ou esta regra possui a propriedade de ser ativada ou

desativada pela ação de uma regra específica.

Modificadores de acesso public ou private são possíveis em rulebases.

Modificadores private impedem regras de outras rulebases referenciarem esta

rulebase (como interface) ou alterarem seu estado (ativar ou desativar, assim como

uma ou outra regra de dentro desta base de regras especificamente).

Modificadores public permitem que outros projetos R# possam realizar estas

operações na base de regras. O modificador padrão (implícito, é válido quando não há

nada escrito) permite que bases de regras num mesmo projeto possam se referenciar,

e que regras em uma base possam modificar o estado de regras da outra, esta última

quando o atributo [Lockable] está declarado. Não é possível ter regras que estão

declaradas em rulebases private referenciadas em outras rulebases abstratas.

3.1.2 Ativar ou desativar regras e rulebases

A Imagem 16 mostra, em quatro exemplos, como ativar ou desativar regras e

rulebases em R#. Os exemplos foram pensados não só para exibir a sintaxe da

linguagem em relação a estas operações, como também para mostrar situações onde a

aplicação deste recurso seja vantajoso.

Imagem 16

32

O exemplo A descreve uma regra cuja definição é que, caso as habilidades

gerais de um jogador passem de determinado ponto, habilita-se um conjunto de

outras regras para promover um nível de dificuldade maior. Isto é, “se os seus skills

aumentarem, ative as regras definidas na base de regras ‘SecondLevelRules’ para este

jogador”. Sua sintaxe mais simples é composta somente pela palavra-chave ‘activate’,

seguida do tipo de elemento que se deseja ativar (pode ser uma base de regras ou

uma regra, e no exemplo é uma base de regras), seguida finalmente pelo nome do

elemento a ser ativado. Praticamente a mesma sintaxe é utilizada para desativar um

elemento, apenas trocando-se ‘activate’ por ‘deactivate’. Ao ativar a base de regras

SecondLevelRules para esta instância da classe Player , por motivos óbvios, apenas as

regras em SecondLevelRules que regulamentam a classe Player terão efeito nesta

operação. Se nesta base houver regras especificando outras classes, elas não serão

ativadas ou desativadas, por não fazer sentido esta operação neste escopo, uma vez

que elas não têm nenhum poder de inferência sobre a classe Player.

No caso B, regras relacionadas a gravidade são desabilitadas para um jogador

quando ele tem a propriedade de flutuar válida. Da forma que está escrita, apenas a

instância de Player que tiver a propriedade de flutuar válida terá a gravidade suspensa.

O exemplo C considera como poderia funcionar o mecanismo num cenário

multiplayer, se houvesse uma regra que, quando um dos jogadores apertasse uma

combinação de botões, o permitisse desabilitar as regras de gravidade para todos os

outros jogadores. A mudança na chamada do deactivate foi apenas no final da sua

declaração, onde foi escrito um ‘forall’. O forall, colocado desta maneira, significa que

para todas as instâncias de Player esta base de regras será desativada.

Há a possibilidade de, ao invés de forall, utilizar um for <instance>, onde

substitui-se <instance> por alguma instância específica que se queira desabilitar aquela

base de regras. Há implicitamente em todas as chamadas que não tiverem forall ou for

<instance> um “for this”, o que quer dizer que estas operações terão reflexo apenas na

instância que a regra que contém a operação regulamenta.

O caso D mostra que também é possível usar forall acompanhado de uma

referência para uma coleção de objetos de uma mesma classe, obrigatoriamente

também sendo da mesma classe regulamentada pela base de regras ou pela regra que

está sendo ativada ou desativada nesta chamada. No exemplo D todos os jogadores

que estão na coleção Players, encontrada na classe GameLogic, têm algumas regras

ativadas após o confronto com o “mestre” da fase começar.

O mesmo atributo [Lockable], que precisa ser relacionado a uma rulebase ou

regra para definir se elas podem ter seu estado de ativação alterado, tem uma

propriedade que precisa ser modificada para permitir que seja possível ativar ou

desativar uma instância sem ser ela própria, caso que acontece no uso de forall em

33

qualquer um de seus modos e no uso de for <instance> onde a instância não for this.

Para permitir tal funcionalidade, deve-se escrever [Lockable(Globally=true)].

3.1.3 Choose

A parte destinada a declaração de que ações serão executadas caso uma regra

seja disparada deve permitir o uso de qualquer tipo de comando C# dentro dela,

porém foi levantada a necessidade de outro elemento específico para regras. A

Imagem 17 mostra um constructo específico da linguagem R# que pode ser adicionado

na parte de ações em uma regra: choose. A principal idéia que motiva sua existência é

facilitar a implementação do não-determinismo através do uso de probabilidade.

Imagem 17

Mesmo que um determinado conjunto de pré-condições seja satisfeito, isso não quer dizer que o especificador deseje que uma regra sempre execute o mesmo tipo de ação. Através do uso de choose, é possível escolher entre duas ou mais ações com uma probabilidade ponderada entre elas.

No exemplo ilustrado pela Imagem 17, a regra AskForSalaryIncrease, quando detecta que o salário está abaixo de um determinado nível e a satisfação do empregado está baixa, não toma uma decisão imediatamente. Com uma probabilidade de 50%, ele escolherá a primeira opção contida no bloco choose, que será enviar um pedido ao gerente de aumento de salário. Com uma probabilidade de 40% ele entrará na segunda opção declarada no choose, e dentro dela verificará se a satisfação está criticamente baixa. Se estiver, pede ao gerente demissão. Como a soma dos valores atribuídos a opção é inferior a 1 (0.50 + 0.40 = 0.90), há uma probabilidade de 10% de o empregado não tomar nenhuma decisão, nenhuma das alternativas ser escolhida.

Ao invés de valores definidos na própria regra como “0.50”, é possível associar à opção uma referência para um valor em uma propriedade ou numa constante de uma classe, por exemplo. Caso a soma dos valores ultrapassar 1 é ignorada a opção de não decidir por nenhuma e há uma reponderação da probabilidade tomando por valor

34

base a nova soma dos valores. Há também a possibilidade de não colocar nenhum valor de probabilidade, descrevendo apenas “option:”. Este caso é adequado quando o desenvolvedor queira dar igual oportunidade entre as escolhas.

Se houver dentro do bloco choose apenas um “option:”, a linguagem atribui 50% de chance desta única opção ser escolhida, uma vez que não faria sentido criar uma estrutura choose-option caso se quisesse com probabilidade de 100% executar um comando. No caso onde se declara em uma ou mais cláusulas (options) a probabilidade e existem outras sem valor associado, o valor restante é dividido para estas. A próxima seção apresenta outro detalhe da linguagem, todavia introduz outro exemplo que também utiliza o bloco choose, dessa vez de maneira mais avançada, de tal modo que a leitura da próxima seção complementa esta explicação.

3.1.4 Declaração

Todas as regras tradicionais, baseadas em casamento de padrões, precisam

realizar a etapa de declaração. É através dela que essas regras descrevem que tipos de objetos da memória de trabalho estão sendo regulamentados por ela. Em regras path-

based esta etapa não é sempre necessária. Pelo fato de uma regra path-based sempre dizer respeito a uma instância específica de uma classe, ela pode fazer o uso (implícito ou explícito) de this, e referenciar diretamente propriedades do objeto.

No entanto, quando a verificação de uma regra envolve uma propriedade de um objeto que está contido em uma coleção pertencente à instância regulamentada, não é possível acessar este objeto sem utilizar um quantificador, o que precisa ser declarado na definição da regra, algo como “considerando algum elemento x desta

coleção, quando x tiver determinada condição verdadeira, aplicar ação”. Por este motivo, as regras em R# possuem, opcionalmente, espaço para declaração, o que está ilustrado no exemplo da Imagem 18.

A regra é para a classe Manager, e tem sua condição verdadeira quando um empregado – que está listado em Employees, propriedade que referencia uma coleção em Manager – possui salário menor que o merecido. É criada uma etapa de declaração, que cria uma variável ‘employee’, definindo a que classe pertence e em que coleção está inserida. É também criado neste exemplo uma variável ‘deservedWage’, que utiliza o método de um objeto de Manager que recebe um empregado e calcula o salário merecido. Esta variável foi criada com o objetivo de mostrar que é possível usar esta etapa para declarar outros tipos de informação e usá-las na etapa de condição ou ação.

Também é interessante permitir a operação entre itens diferentes numa mesma coleção, bastando na declaração fazer algo como “Employee emp1, emp2 in

Employees” e em seguida compará-los, e a linguagem não comparará uma instância com ela mesma nesta situação. Esta operação recupera a expressividade de pattern-matching em potencial, com a vantagem de garantir que não serão confrontados elementos que realmente não se relacionam, nem diretamente nem possuem uma relação com um objeto em comum.

35

Imagem 18

Esta regra foi definida desta forma também para exemplificar o uso mais avançado de choose. Há um aninhamento de chooses para representar o seguinte raciocínio: quando o salário que o empregado tem é menor que o merecido, a regra dispara. De acordo com uma probabilidade “ActionProbabilities.SalaryIncrease”, realiza o aumento para o salário merecido. Com probabilidade complementar, faz o seguinte: verifica se o salário do empregado está criticamente abaixo do merecido (no caso, estabelecido como limite 70% do merecido). Caso isso ocorra, com probabilidade “ActionProbabilities.UrgentSalaryIncrease” ele aumenta. Ou seja, há uma possibilidade de ele não aumentar o salário ainda assim. ActionProbabilities neste caso representa uma classe que contém pelo menos duas propriedades de acesso global: SalaryIncrease e UrgentSalaryIncrease.

3.1.5 Outras considerações sobre o uso da linguagem

É possível utilizar dentro do código C# que está nas regras R# referências para

classes que utilizam o design pattern Singleton, assim como acessar constantes e

propriedades estáticas em geral em classes, em todas as etapas. É possível chamar

métodos estáticos na etapa de ação de qualquer regra R#. Também é possível, na

declaração de uma regra, o uso de final e override, para controle de herança entre

regras. Ao usar final na declaração de uma regra, impede-se que ela seja modificada

em uma classe derivada. E ao usar override, realiza-se a sobrescrita.

36

3.2 Mecanismo de inferência de regras

Esta seção explica como deve ser o funcionamento do mecanismo de inferência

de regras em R#. Ao contrário do JEOPS e a maioria das outras soluções de regras, não

há, em R#, um único motor centralizado. O código R# compilado está distribuído nas

classes compiladas de cada objeto regulamentado em C#, processo ilustrado pela

Imagem 19.

Imagem 19

O processamento de regras em R# pode ser entendido através de três fases:

disparo, avaliação e execução. Uma regra é disparada quando um objeto

regulamentado tem um dado modificado e a propriedade que encapsula o acesso a

este dado é considerada nas condições desta regra. Para o disparo ocorrer, é

necessário um monitoramento da propriedade que pode modificar este dado, que

pode ser considerado como uma “fase 0” que desencadeia o início das fases de

processamento de regras. Uma regra também pode ser disparada quando uma nova

instância de sua classe associada é criada. No paradigma path-based este tipo de

disparo é denominado relevant construction (o outro acima citado é chamado de

relevant change), e seu monitoramento implica na necessidade de, a cada nova

instância criada, verificar todas as regras.

Quando uma regra é disparada, segue-se a sua fase de avaliação. Se todas suas

condições forem verdadeiras, esta passa para a fase de execução e tem suas ações

executadas. A execução da ação pode disparar outras regras, causando um efeito de

“forward chaining” na execução de regras. Considerando este efeito, é preciso garantir

que uma regra não seja executada baseada em valores antigos ou mais de uma vez

sobre a mesma modificação.

Por exemplo, se uma mudança num dado de um objeto dispara duas regras de

forma que as duas têm suas condições satisfeitas, após a execução da primeira, a

37

segunda pode não ter mais como verdadeiro o resultado da avaliação de suas

condições. O mecanismo deve agir de forma segura com relação ao forward chaining.

O uso de um contador sobre as mudanças de cada propriedade é a solução

usada por R++, que pode ser aplicada em R#. Cada propriedade monitorada tem um

contador de modificações, e cada regra tem um outro contador para cada propriedade

de sua condição, guardando as versões avaliadas ou executadas por ela, de tal forma

que comparar os dois valores é suficiente para resolver o problema.

A Imagem 19 ilustra que partes previamente definidas da classe Employee uma

regra AskForSalaryIncrease deve monitorar para tornar a inferência sensível aos dados

especificados em suas condições.

No caso do construtor Employee, porque um novo elemento deve, de qualquer

maneira, sempre ser verificado em relação às condições de todas as regras que o

regulamentam. Em relação à property Wage, quando, em algum lugar do sistema,

usarem este encapsulamento para modificar o valor de wage, esta deverá avisar às

regras relacionadas a mudança, o que tem como conseqüência a verificação das

condições das regras que consideram Wage no seu left-hand-side.

No tocante à property Satisfaction, pelo fato de ela ser um encapsulamento

para uma outra estrutura, o uso combinado do monitoramento da property com a

criação de triggers para os métodos Add, Remove e dos operadores de mudança do

valor de uma chave em uma coleção Dictionary são suficientes para fazer as regras

devidas serem verificadas quando houver mudança. Esta última alteração, apesar de

não ser trivial devido ao encapsulamento do comportamento das coleções em C#,

pode ser realizada de várias formas, desde que os dados só estejam disponíveis a

classes externas através do encapsulamento. Uma dessas formas seria a substituição

de Dictionary por uma estrutura que estendesse seu comportamento, sendo esta

alteração realizada automaticamente, em tempo de compilação, por R#.

Algumas novas estruturas são necessárias para o controle de tais regras, dada a

necessidade de armazenar informações como “esta regra está ativada para esta

instância” ou “esta regra está ativada para instâncias desta classe”, assim como

também informações sobre que regras devem ser verificadas quando determinadas

propriedades são modificadas. Todas elas não precisam, no entanto, estar guardadas

nas classes as quais as regras se referem. Podem ser armazenadas em gerenciadores

deste tipo de informação disponibilizados por uma biblioteca fornecida pelo R#, ao

invés de dispersas nas classes regulamentadas. Esta decisão impactaria numa menor

alteração no código C#, também trazendo como conseqüência uma centralização

parcial dos dados de inferência. Como estas informações dizem respeito unicamente

ao mecanismo de raciocínio baseado em regras implementado, pode ser considerada

38

adequada esta centralização resultante, pois faz com que a instrumentação de código

C#, que causa modificações no código base, só seja necessária para a etapa de

monitoramento dos dados, isto é, dos “fatos”.

Outros dados, no entanto, encontram uma solução mais eficiente se estiverem

nas classes regulamentadas. O exemplo ilustrado pela Imagem 20 considera a situação

em que uma regra tem em sua condição não apenas as propriedades diretas de uma

classe, mas propriedades de outras classes pertencentes à instância da classe

regulamentada. A propriedade Happiness está na classe Mood, que mesmo não sendo

a classe diretamente regulamentada pela regra AskForBreak, tem a modificação no

dado encapsulado por Happiness monitorada. Para este monitoramento dar origem ao

disparo da regra AskForBreak em relação a instância de Employee que tiver

Mood.Happiness modificado, a classe Mood precisa armazenar, para cada instância

sua, uma referência para o objeto raiz deste caminho “Mood – Happiness”.

Imagem 20

Considerando um caminho maior, como “Department – Director – Mood –

Happiness”, cada um destes, menos o “this”, terão uma referência para sua raiz

imediata, ou seja: no caso de Happiness, Mood; no caso de Mood, Director. Por este

tipo de dado estar relacionado diretamente à etapa de monitoramento, encontra sua

melhor maneira de armazenamento dentro das classes direta ou indiretamente

consideradas na etapa de avaliação das regras.

A integração de um projeto de regras R# aos elementos regulamentados na

solução C#, devido às possibilidades permitidas pela plataforma .NET, podem ser

realizadas de duas maneiras: em tempo de compilação ou em tempo de execução. As

duas formas apresentam vantagens e desvantagens.

39

A Imagem 21 mostra a maneira de realizar a integração em tempo de

compilação. Explicando o exemplo, trata-se de uma solução com dois projetos: um

deles em C# chamado Game, e o outro sendo um projeto em R# que especifica regras

para Game, chamado GameRules. Inicialmente, o projeto Game é compilado, o que

gera um assembly em .NET.

Este assembly tem seu código modificado na etapa de compilação de R#, que

adiciona ao código existente o comportamento específico de monitoramento, isto é,

acrescenta código nas propriedades consideradas nas condições, nos construtores de

objetos relacionados às condições das regras e cria nas classes de objetos

indiretamente referenciados na etapa de avaliação referência para o(s) objeto(s)

raiz(es) da sua propriedade. O plural é válido pois um mesmo objeto ‘a’ pode ser

referenciado por dois outros ‘b’ e ‘c’, e estes estarem sobre a influência de uma

mesma regra que considere ‘a’.

Imagem 21

Também faz parte da modificação uma operação, esta mais delicada, com o

tratamento em relação ao monitoramento de coleções. Ao invés de usar as coleções

normais de C#, é possível, como estratégia para monitorar a inserção ou remoção de

um elemento na coleção, estender o comportamento das coleções existentes em C#

adicionando o código de monitoramento nos métodos que realizam estas operações.

Estas coleções estendidas podem estar disponíveis na biblioteca disponibilizada por

R#, e a instrumentação apenas substituir as estruturas iniciais pelas estendidas.

Ressaltando que esta substituição é uma alternativa possível apenas se for respeitado

o encapsulamento no acesso a estas estruturas. A plataforma .NET apresenta outras

alternativas que envolvem o uso da .NET Profiling API, no entanto utilizar esta

40

biblioteca, ao menos no momento da análise decorrente do presente estudo, pareceu

uma solução menos viável por conta da sua complexidade.

A Imagem 22 mostra a maneira de realizar a integração em tempo de execução.

Durante a etapa de compilação, ao invés de modificar o assembly do projeto Game, o

projeto GameRules gera um novo assembly. Este contém o gerador de mudança de

código que só é realizada em tempo de execução.

Imagem 22

A vantagem do segundo modelo de integração está em não modificar o código compilado do projeto C# em tempo de compilação. Assim, seu assembly não fica necessariamente submetido às regras do projeto GameRules. O release deste assembly fica mais limitado à produção resultante do projeto C#, sem sofrer modificações realizadas por outros agentes externos.

No entanto, o fato de realizar este processo em tempo de execução implica na necessidade de se chamar explicitamente, do projeto executável na solução, um controlador da integração (fornecido pelo compilador de C#), que carregará o projeto R# contido no .dll informado e integrará suas alterações no assembly de Game. Isto também pode ser causador – a depender do tamanho das bases de regras contidas no projeto e da performance da implementação do compilador – de uma lentidão na inicialização da solução que usar este tipo de estratégia.

O primeiro modelo surge mais adequado quando não há uma preocupação aguda em relação a questões de acoplamento, de modo que quando for executado este tipo de integração, o assembly do projeto C# nunca funcionará sem R#. Apresenta a vantagem de, no máximo, transferir uma possível lentidão na compilação para o tempo de compilação, mais aceitável que uma demora na sua execução.

Não há uma melhor escolha genericamente. As duas formas são complementares, pois cobrem os dois tipos de decisão de projeto possíveis nesta situação.

41

4. Implementação de R#: Proof-of-Concept

Devido à alta complexidade de desenvolver a solução R# em um período de

tempo tão curto como o deste projeto, foi realizada uma implementação sem o

objetivo de lançar R# como produto, mas com a intenção de provar o conceito da

proposta, mostrando que é possível realizar esta integração em C#, da forma que foi

apresentada. O foco do trabalho não era implementar uma solução de regras, foi

estabelecido desde o princípio que o foco estava na análise dos conflitos existentes e

proposta de uma solução otimizadora da resolução destes. Portanto, a elaboração da

solução criou uma proposta de escopo amplo. Somente a implementação completa do

compilador de R# já seria demasiado extensa, pelo simples fato de haver uma

aceitação na integração de todos os constructos da linguagem C#.

A implementação PoC (proof-of-concept) de R# tem sua solução dividida em

dois projetos principais, ilustrados na Imagem 23.

Imagem 23

O projeto RuleSharpEngine contém classes que provêem funcionalidade para as

classes C# regulamentadas. O projeto RSharpLang contém a implementação PoC do

compilador de R#, utilizando a plataforma ManagedBabel do Software Development

Kit do Visual Studio 2005 [13]. A decisão de implementação do projeto PoC levou em

consideração a centralização do máximo possível de código no framework de R#

(contido em RuleSharpEngine), deixando a cargo da instrumentação programada pelo

compilador no código C# apenas o código de monitoramento que é necessário para

disparar as regras (fase 0). Alguns dados de controle deveriam ser inseridos no objeto,

mas por simplificação estes dados estão centralizados no motor. Este fato criou a

necessidade de um mecanismo, como existente no JEOPS, de conter uma base de

objetos regulamentados. Para este projeto foi apenas considerada a integração das

regras com os projetos C# envolvidos em tempo de execução.

A Imagem 24 contém o código necessário para realizar esta integração PoC

entre regras e objetos. No método main do projeto C#, inicializou-se um objeto

RuleEngine, motor responsável pelo controle da inferência, na linha seguinte

adicionou-se as rulebases contidas no assembly ProjectRules.dll, sendo necessário

colocar o caminho completo do arquivo para ser carregado, informação omitida na

figura por questões de legibilidade e espaço.

42

Imagem 24

Em seguida, criou-se um objeto de uma classe regulamentada pelas regras de

ProjectRules, chamada SyntheticAgent. Inicialmente, Wage tem valor 100 e Angry é

false. O que este teste fez foi baixar o valor de Wage para 75, e o motor de inferências

de regras modificou, por conter a regra descrita na Imagem 25, Angry para true. No

final da execução deste método, é impresso “Agent got angry”.

O código que está entre ‘//@’ significa que é autogerado por R# em tempo de

execução. Portanto, apenas no momento de runtime será adicionado esta linha, que

associa ao agent a referência para o método de ruleEngine que ele deverá chamar

quando tiver detectado uma mudança em alguma propriedade sua. Quando Wage for

modificada, o agent chamará este método, passando como parâmetro sua instância e

especificando que propriedade foi modificada.

Imagem 25

43

A Imagem 26 exibe os membros públicos das principais classes que compõem o

projeto RuleSharpEngine. A intenção não é de cobrir toda a implementação contida

nelas, mas explicar como se comportam e para que servem suas instâncias.

Imagem 26

A classe principal do projeto é RuleEngine. Ela é responsável por controlar os

objetos regulamentados e conectá-los ao comportamento automático descrito através

das regras, se houver. Provê funcionalidades para incluir bases de regras dentro de um

projeto (podendo ser várias), podendo receber vários assemblies de projetos de

regras. A maior parte do ciclo de inferência de regras é nela definido.

ClassRules é uma classe que contém um conjunto de regras descrito para uma

classe em específico. Serve de estrutura para as classes RuleEngine e RuleBasedObject.

A classe Rule representa uma regra, com suas condições e ações, assim como um

conjunto pré-processado de que propriedades que, quando modificadas, podem afetar

o resultado da verificação de suas condições. As propriedades Condition e Action são

de tipos delegates definidos, de modo que dentro do delegate Condition pode constar

qualquer código C# de avaliação desde que se retorne no final um resultado booleano,

e dentro do delegate Action pode constar qualquer código, e não há retorno.

A classe RuleBasedObject representa um objeto submetido à inferência de

regras no RuleEngine. Guarda uma referência para o objeto no mundo real e as regras

que são válidas para ele. Tem um método GetRelatedRules, que ao receber o nome de

uma propriedade sua (ou de um objeto seu em última instância), retorna as regras nas

quais esta propriedade ocorre no left-hand-side. Contém ainda um método

GetSelectedRules, que tem como entrada um conjunto de regras e retorna as regras

que possuem sua condição verificada como verdadeira para aquele objeto. A

RuleEngine realiza esta operação, mediada pelo RuleBasedObject. Quando o

GetSelectedRules retorna mais de uma regra, é acionado uma estratégia de resolução

44

de conflito, que pode ser customizada pelo usuário (ver Imagem 27), e em seguida,

após solicitar à estratégia de resolução de conflitos a regra escolhida para ser

executada, é chamado no RuleBasedObject o método Apply, que recebe a regra

escolhida e a aplica na instância cujo método foi chamado.

Imagem 27

O projeto RSharpLang, por sua vez, contém o material necessário, de acordo

com o framework Managed Babel [13], para a compilação de um projeto R#. Tem

como seus recursos principais:

• Um arquivo “lexer.lex”, que especifica os tokens da linguagem R#

(numa implementação real, também especifica os tokens de C#, por

extensão), e é, através do gerador MPLEX [14], base para a geração

automática de um Scanner.

• Um arquivo “parser.y”, que define a gramática da sintaxe R#. Também é

necessário numa implementação completa de R# definir toda a

gramática da sintaxe de C#, além de possíveis restrições para a aplicação

desta gramática em cada parte de uma regra em R# (exemplo: no left-

hand-side de uma regra só podem estar expressões de avaliação de C#).

O arquivo é, através do gerador MPPG de Managed Babel [15],

transformado em uma classe em C# que realiza o parsing.

• Uma classe Configuration, que define, além de nome e extensão da

linguagem, outras informações, como por exemplo a coloração das

palavras-chave que terão destaque na linguagem.

• Uma classe Resolver, que, a partir da árvore gerada pelo parser, realiza

a compilação da forma definida pelo usuário do framework.

O material gerado pelos dois projetos não foi suficiente, como já dito, para uma

versão de R# equivalente à proposta realizada. Também não é possível, a partir do

material implementado, tornar o PoC de R# viável para utilização em projetos reais.

Sua aplicação pode ser realizada em projetos experimentais, que tenham como

objetivo principal não seu usufruto simplesmente, mas a continuação do seu

desenvolvimento numa direção de finalização do produto R#.

45

5. Conclusões

A realização deste trabalho foi muito importante para o entendimento claro

dos conflitos existentes entre os paradigmas de regras e de orientação a objetos. A

análise de duas alternativas mais significativas de integração – o JEOPS, sucessor do

NéOPUS, e o R++ – possibilitou, no confrontamento de suas abordagens e crenças, a

definição de uma solução coerente em relação aos resultados obtidos da avaliação.

Embora a implementação de R# tenha sido apenas uma prova de conceito e

não tenha, na prática, o poder de especificar as regras R# tais como elas foram

propostas neste trabalho, esta implementação foi suficiente para delinear os passos a

serem seguidos num futuro desenvolvimento real da solução em C#.

As decisões tomadas pela solução R# poderão implicar numa aceitação pouco

unânime por parte da comunidade de programadores de bases de regras, pois estes

muitas vezes estão acostumados a especificar conhecimento a partir de outros

paradigmas que R# não suporta. Todavia, o fato da linguagem ter seu objetivo

alcançado na dimensão da uniformidade de integração é considerado mais relevante.

Diversos trabalhos futuros são possíveis a partir deste que foi realizado. O

primeiro deles seria a implementação de fato da solução R#, lançá-la como produto. A

criação de uma solução integrada ao Visual Studio de Debug também ajudaria bastante

no desenvolvimento de regras. Aplicações que criem diagramas que exibam

visualmente a modelagem de regras, preferencialmente contendo a relação com as

classes C# regulamentadas, pode ser de grande valia para um projeto que utilize R#.

Pelo fato de C# ser uma linguagem sempre em evolução, trabalhos futuros também

incluiriam a adequação de R# a novos elementos da linguagem hospedeira.

A proposta da solução R#, além de resolver de maneira cuidadosa as diferenças

entre os paradigmas integrados, também promoveu, por aderir não só tecnicamente à

linguagem, o seguimento mais amplo da filosofia de C#. Esta adesão resultou num

mecanismo de definição de regras de alto nível, com constructos que permitem uma

expressividade maior na especificação de conhecimento implícito, habilitam

programadores de bases de regras a tomarem decisões de arquitetura mais elaboradas

e ampliam as possibilidades de inserção de não-determinismo através de regras. Com

R# é possível definir uma base de conhecimento dinâmica, onde conhecimentos

específicos têm sua inferência ativada apenas a partir de um dado momento, e este

fator é um dos maiores diferenciais da solução. A criação do constructo choose

também tem grande valor no incremento de expressividade em regras. O trabalho,

desta forma, apontou com um bom grau de inovação e originalidade sobre o estado da

arte dos mecanismos existentes para a integração entre regras e objetos.

46

6. Referências

[1] ROLF, Bertil (2004) Two Theories of Tacit and Implicit Knowledge. Blekinge

Institute of Technology, Ronneby Sweden.

[2] BOUAUD, J.; VOYER, R. (1994) Behavioral match: embedding production systems and objects. Proceedings of OOPSLA’94 Workshop on Embedded

Object-Oriented Production Systems. Paris: Laforia.

[3] D’HONT, Maja; GYBELS, Kris; JONCKERS, Viviane (2004) Seamless Integration of Rule-Based Knowledge and Object-Oriented Functionality with Linguistic Symbiosis. SAC ’04, March 1417.

[4] CLANCEY, William J. (1981) The Epistemology of a rule-based expert system: a framework for explanation. Report No. STAN-CS-81-896. Stanford University,

Stanford CA.

[5] SHOHAM, Y. (1993) Agent Oriented Programming. Artificial Intelligence 60 51–

60.

[6] ALBAHARI, Ben (2000) A comparative overview of C#. Disponível em

http://genamics.com/developer/csharp_comparative.htm . Acessado em junho

de 2008.

[7] FIGUEIRA, Carlos (2000) JEOPS - Integração entre objetos e Regras de produção em Java. Dissertação de Mestrado, CIn – UFPE.

[8] BARBOSA, Pablo (2006) CEOPS++ - Integração entre Objetos e Regras de Produção em C++. Trabalho de Graduação, CIn - UFPE.

[9] PACHET, François (1995) On the Embeddability of Production Rules in Object-Oriented Languages. Journal of Object-Oriented Programming, vol. 8, n. 4.

[10] CRAWFORD, James M., et al. (1996) Path-Based Rules in Object-Oriented Programming. Thirteenth National Conference on Artificial Intelligence (AAAI-

96).

[11] FORGY, Charles (1982) Rete: A Fast Algorithm for the Many Pattern/Many Object Pattern Matching Problem. Artificial Intelligence 19 17–37.

[12] LITMAN, Diane, et al. (2002) R++: Adding Path-Based Rules to C++. Knowledge

and Data Engineering, vol. 14 n. 3 638-658.

[13] GOUGH, John (2006) Managed Babel Overview. Queensland University of

Technology, Brisbane Australia. July 21, 2006

[14] GOUGH, John (2006) The MPLEX Scanner Generator. Queensland University of

Technology, Brisbane Australia. August 21, 2006

[15] GOUGH, John; KELLY, Waine (2006) The MPPG Parser Generator. Queensland

University of Technology, Brisbane Australia. August 21, 2006

47

Assinaturas

____________________________________

Geber L. Ramalho (Orientador)

____________________________________

Victor Cavalcanti Rodrigues (Aluno)