Atenção: esse artigo tem um vídeo complementar. Clique e assista!

De que se trata o artigo:

Apresentamos uma referência da linguagem JavaFX Script, que faz parte da plataforma JavaFX.


Para que serve:

JavaFX é a nova plataforma RIA da Sun, compatível com Java SE e Java ME, já apresentadas em artigos anteriores desta série. Alguns leitores podem encarar a exigência de aprender uma nova linguagem de programação como um obstáculo, mas este aprendizado é facilitado pela semelhança entre JavaFX Script e Java – tiramos proveito desta semelhança para explicar a nova linguagem de forma concisa. Mesmo para quem já aprendeu JavaFX Script, o artigo serve como uma referência bem organizada e pragmática (ao contrário da referência oficial, que tem uma estrutura mais formal, certamente mais detalhada, mas não adequada à facilidade de consulta e sem pretensões didáticas).


Em que situação o tema é útil:

A linguagem JavaFX Script é um pré-requisito para desenvolver para a plataforma JavaFX, a qual promete grande produtividade, mas (com qualquer plataforma) somente após vencer uma curva inicial de aprendizado.

Mesmo para quem não tiver grande interesse na JavaFX, o artigo apresenta muitas inovações da JavaFX Script que são um aprendizado valioso para qualquer interessado em linguagens de programação.

Na Edição 67 apresentamos a plataforma JavaFX, introduzindo-a de forma conceitual e dando alguns “aperitivos” das suas funcionalidades e programação. Na Edição anterior (68), seguimos com um primeiro tutorial de programação JavaFX, ensinando o básico da linguagem e framework de uma forma prática, mas bastante informal.

Agora, encerrando esta cobertura inicial da JavaFX, vamos nos focar apenas na linguagem JavaFX Script, cobrindo-a de uma forma mais formal e abrangente. (Para o leitor ainda pouco motivado a aprender JavaFX Script, seria boa idéia começar pelo quadro “Por que uma nova linguagem?”.)

Não é meu objetivo repetir o documento de Referência da Linguagem de Programação JavaFX Script que está disponível em openjfx.dev.java.net/langref. Ao invés disso, a idéia é explicar a linguagem de uma forma concisa e pragmática, tendo como alvo não alguém que vai escrever um compilador ou outra ferramenta que exige conhecimento formal da linguagem, mas um desenvolvedor que deseja vencer a curva de aprendizado da sua sintaxe ou então ter uma referência de fácil consulta. Ainda mais especificamente, escrevo pensando no programador que já conhece bem a linguagem Java, o que permitirá resumir ou omitir explicações dos pontos onde ambas as linguagens forem iguais ou muito parecidas (e são muitos pontos). Assim, podemos concentrar nosso tempo e neurônios nas partes que são diferentes.

O artigo é organizado como uma referência da linguagem, agrupando suas funcionalidades em seções como Valores, Operadores, Classes etc., de forma similar a uma referência formal. No entanto, fiz este agrupamento de uma forma “relaxada” segundo critérios conceituais e não de gramática formal. Por exemplo, a seção de Operadores não possui todos os operadores, pois alguns deles são cobertos em outras seções (ex.: os diversos operadores específicos para sequences são mostrados na seção Sequences). Também não me preocupei em documentar minimamente alguns aspectos mais elementares da linguagem que são exatamente idênticos a Java, por exemplo a sintaxe para números literais. A intenção é ter um texto o mais compacto e didático possível, mas ainda assim, suficientemente organizado e formal para servir como referência da linguagem.

Nota: no decorrer do artigo, uso o termo “JavaFX” no lugar de “JavaFX Script”, por simplicidade.

Tipos e Valores

O sistema de tipos da JavaFX é inspirado no Java, mas com melhorias importantes. Na Tabela 1, classifiquei os tipos da JavaFX em cinco categorias e comparei-os aos tipos equivalentes em Java. Vamos comentar apenas as novidades desta tabela.

Valores

A maior novidade é que o typesystem de JavaFX é OO-puro: não existem tipos primitivos. Por outro lado, existem “tipos de valor”, que são aqueles cuja igualdade de referência é equivalente à igualdade de valor (em outras palavras: x == y se e somente se x.equals(y)). Os tipos primitivos do Java, como int, são tipos de valor, por isso não há dois valores 17 distintos, por exemplo. O mesmo vale para o Integer da JavaFX. Além disso, os valores são imutáveis, e não admitem null.

A diferença entre esses tipos da JavaFX e os tipos primitivos do Java é que os tipos de valor são objetos. Em JavaFX, todos os tipos, sem exceção, são derivados de Object.

Em termos de implementação, os valores de JavaFX são equivalentes aos primitivos de Java, evitando o custo de desempenho de usar objetos. Por exemplo, se você olhar o arquivo .class gerado pelo javafxc para uma função que recebe um parâmetro Integer, verá que o bytecode compilado irá conter um método que recebe um int primitivo. Somente quando um valor é utilizado em alguma operação que exige sua faceta OO, a JavaFX faz uma conversão automática para um objeto. Você pode considerar isso uma versão melhorada do autoboxing de Java 5. As melhorias incluem a simplificação (não há tipos paralelos como int/Integer) e desempenho (o compilador, runtime e frameworks usam várias técnicas para representar valores na forma primitiva, e reduzir ao mínimo as conversões de/para a forma OO).

VALORES (value types)

Comentários

JavaFX

Java

true ou false

Boolean

boolean

Inteiro, 8 bits sem sinal

Byte

byte

Inteiro, 16 bits com sinal

Short

short

Inteiro, 32 bits com sinal

Integer

int

IEEE754, 32 bits

Number, Float

float

IEEE754, 64 bits

Double

double

String

String

(Em Java, é tipo de referência)

Duração temporal

Duration

N/A; costuma-se usar long

VOID

Comentários

JavaFX

Java

Nenhum valor

Void

void

REFERÊNCIA

Comentários

JavaFX

Java

Classe

class

class, interface

Função

function (parâmetros) : retorno

N/D

Seqüência/Array

TipoElemento []

TipoElemento []

Tabela 1. Classificações de tipos da JavaFX, comparativamente com Java.

Strings

Menção honrosa vai para o tipo String da JavaFX Script, que ao contrário de Java, é definido como um value type. Além disso, carrega um lote de funcionalidades extra:

• O valor default de uma String não inicializada é "" (string vazia). É impossível ter uma string null; se você fizer esta atribuição, fica com "". Nunca acontecerá uma NullPointerException envolvendo strings (ou qualquer value type);

• O operador == equivale a equals(), não há distinção entre igualdade e identidade (novamente, como todos value types);

• Pode-se mesclar strings com expressões, por exemplo: "Alô, {nome}!" será expandido conforme o valor dinâmico da variável nome. Ou então, "Temperatura: {%2.2f temp}", a variável temp será formatada no estilo de System.printf() (ex.: %2.2f 32.257 → 32,25);

• Pode ser delimitada por aspas simples ou duplas, ex.: 'String com "aspas duplas" dentro';

• Internacionalização é simples: def msg = ##"ERRO" irá atribuir a msg o valor da string associada à chave ERRO no resource bundle da aplicação; se não existir este resource, o valor da string será "ERRO" mesmo. Para separar a chave do valor default, use ##[ERRO]"Deu zebra!";

• É o único tipo de JavaFX Script para manipular caracteres. Não existe um tipo específico para um caractere único, como o char do Java. Conversões automáticas são feitas ao invocar classes/APIs de Java que utilizam char; ex.: "Osvaldo".charAt(0) = "O".

Duration

O tipo de valor Duration representa uma quantidade de tempo. Exemplos de literais: 15ms (15 milissegundos), 2.3s (2.3 segundos), 55m (55 minutos), 10h (10 horas), 10h + 5m (10 horas e 5 minutos). Este tipo não passa de um açúcar sintático para um long contendo o tempo normalizado para milissegundos, mas ao usar o framework de animação da JavaFX, você verá que é muito conveniente ter uma sintaxe especial para isso.

Void

O Void do Java FX significa o mesmo que o do Java, mas é mais útil, ver seção Controle.

Referência

Os reference types da JavaFX também são familiares. Ver seções sobre funções e sequences.

Funções

JavaFX Script possui funções de primeira ordem. Exemplo:

var validador: function (o:Object): Boolean

O tipo da variável validador é uma função que possui um parâmetro do tipo Object e retorna um Boolean. Portanto, funções podem ser atribuídas a variáveis, passadas como parâmetro, etc. como quaisquer outros tipos. A linguagem Java possui uma aproximação desta facilidade, combinando interfaces com inner classes – mas o resultado é uma sintaxe confusa. Com a versão muito melhorada de JavaFX, podemos escrever por exemplo um tratador de eventos assim:


  Stage { 
      onClose: function() { FX.exit() }
  }

Funções suportam os seguintes modificadores (antes do function):

• Visibilidade (public, protected, package, e por default script-private);

• abstract, igual a Java;

• override, igual ao @Override do Java 5 (mas é mandatório);

• bound, que examinaremos em Binding;

• Não há nada equivalente ao final dos métodos Java.

Sequences

As sequences da JavaFX Script possuem sintaxe similar, mas significado e capacidades diferentes dos arrays do Java. Em comum com os arrays, as sequences são coleções de objetos de um tipo homogêneo e com tamanho fixo, indexadas a partir da base 0.

SEQUENCES

Expressão

Resultado

def dias : String[] = [ "Dom", "Seg", "Ter" ]

def dias = [ "Dom", "Seg", "Ter" ]

[ "Dom", "Seg", "Ter" ]

sizeof dias

3

dias[1]

"Seg"

dias[1 .. 2]

dias[1 .. <3]

["Ter", "Qua"]

dias[d | d.startsWith("T")]

["Ter"]

dias[0] = "DOM"

["DOM", "Seg", "Ter"]

insert "Qua" into dias

insert "Qua" before dias[3]

insert "Qua" after dias[2]

["Dom", "Seg", "Ter", "Qua"]

insert ["Qua", "Qui"] into dias

[ dias, ["Qua", "Qui"] ]

["Dom", "Seg", "Ter", "Qua", "Qui" ]

delete dias[1]

delete "Dom" from dias

["Seg", "Ter"]

delete dias[1 .. 2]

["Seg"]

delete dias

[]

for (d in dias)

d.charAt(0)

[ "D", "S", "T" ]

(for generator, retorna uma sequence com os

elementos produzidos por cada iteração)

for (d in dias where d.startsWith("T"))

d.charAt(0)

[ "T" ]

[ 1 .. 100 ]

[ 1, 2, 3, 4, .., 98, 99, 100 ]

(É na verdade uma instância de Range,

não consome memória com a lista de elementos)

[ 1 .. 100 step 10]

[ 1, 11, 21, 31, 41, 51, 61, 71, 81, 91 ] (Novamente Range)

dias = "Sáb"

[ "Sáb" ]

(Uma sequence de um único elemento pode ser

Inicializada/atribuída sem os ‘[]’)

reverse dias

[ "Ter", "Seg", "Dom" ]

[ 1, null, 2 ]

[ 1, 2 ] (Nulos não são suportados)

Tabela 2. Sintaxes de uso de sequences.

As diferenças começam com algumas sintaxes facilitadas (syntax sugar), mas vão além. A Tabela 2 exemplifica todas as capacidades e sintaxes específicas para sequences. Algumas coisas são simples – por exemplo, o operador sizeof equivale ao pseudo-atributo length dos arrays do Java; e com o ‘..’ você pode especificar ranges (intervalos), que podem ser usados tanto para determinar quais elementos da sequence serão manipulados (como em delete dias[1 .. 2]), quanto para criar uma sequence contendo todos os números inteiros do range (como em [1 .. 100]).

Uma característica importante das sequences é que elas não admitem aninhamento; qualquer tentativa de colocar uma sequence dentro de outra irá gerar uma sequence única com todos os elementos. Por exemplo, o resultado de [ dias, ["Qua, "Qui"] ] não é [ "Dom", "Seg", "Ter", ["Qua, "Qui"] ], e sim [ "Dom", "Seg", "Ter", "Qua, "Qui" ]. Esta transformação é conhecida como flattening (achatamento), e embora possa parecer uma limitação, é uma característica importante do estilo de programação da linguagem. Este estilo incentiva o uso de expressões “fluentes” – onde o output de cada expressão é usado como input da seguinte, algo similar ao uso de pipes em shell scripts. Para isso funcionar, é preciso que todas as operações utilizem uma estrutura de dados padronizada; no Unix essa estrutura é texto ASCII, em Java FX Script (e outras linguagens) é a lista ou sequence.

O leitor expert em outras linguagens que fazem uso pesado de listas/sequences, como as da família LISP, sabe que tais linguagens permitem o uso de estruturas aninhadas. Mas JavaFX Script não pretende ser uma linguagem de programação funcional sofisticada, para quem curte técnicas do tipo “map/reduce”; ao invés disso, foi feita a opção por sequences com flattening automático, o que simplifica os cenários de uso mais importantes para JavaFX, por exemplo a inicialização de estruturas complexas como o Scene Graph.

Observe a tentativa da JavaFX Script de usar uma sintaxe SQL-like para a manipulação de dados: sintaxes como insert..into, delete..from, o in e o where do for.

Num detalhe importante, o leitor pode estar confuso com minha afirmação que as sequences são imutáveis, seguida por uma tabela que indica várias sintaxes para sua alteração. Explico: todas essas sintaxes criam uma nova sequence. Por exemplo:

var dias = [ "Dom", "Seg", "Ter" ];

  var diasUteis = dias;
  delete "Dom" from diasUteis;
  println(diasUteis); // ["Seg", "Ter" ]
  println(dias); // [ "Dom", "Seg", "Ter" ]

No exemplo acima, após a atribuição diasUteis = dias, ambas variáveis apontam para a mesma sequence. Mas em seguida, o delete cria uma nova sequence e a atribui à variável diasUteis; a sequence original continua existindo pois ainda é referenciada por dias. Este truque sintático – operadores como delete e insert, que executam uma atribuição ‘escondida’ – mantêm a sintaxe familiar para quem não está acostumado ao estilo funcional (no qual nunca, ou raramente, se altera o valor de variáveis preexistentes). Para mais detalhes, veja o quadro “Imperativo ou Funcional?”.

Declarações

Nesta seção veremos como JavaFX Script permite declarar variáveis e funções.

VARIÁVEIS E ATRIBUTOS

Expressão

Significado

var x

Ilegal

var x : Integer

Declara variável mutável, de tipo Integer, inicializada

com o valor default para o seu tipo (no caso, 0)

var x = 5

Declara e inicializa variável mutável, de tipo Integer

var x : Integer = 5

Declara e inicializa variável mutável, de tipo Integer

def x = 5

Declara e inicializa constante, de tipo Integer

def x : Integer = 5

Declara e inicializa constante, de tipo Integer

def x

Ilegal, valor é exigido

def x : Integer

Ilegal, valor é exigido

for (d in dias) { d = "Domingo" }

Ilegal: é proibido modificar “variáveis induzidas”,

como a variável de iteração de um for

FUNÇÕES

Expressão

Significado

function dobro (a : Integer) : Integer

{ return a * 2; }

Declara uma função com um parâmetro Integer e tipo de retorno também Integer

function soma (a : Integer, b : Integer)

{ return a + b; }

Declara uma função com dois parâmetros Integer e tipo de retorno também Integer

function alo (nome : String)

{ println("Alo, {nome}"); }

Declara uma função com um parâmetro String e tipo de retorno Void

function alo (nome)

{ println("Alo, {nome}"); }

alo("Osvaldo");

Declara uma função com um parâmetro String e tipo de retorno Void

var f : function (Double) : Boolean

Declara uma variável cujo tipo é uma função com um parâmetro Double e tipo de retorno Boolean

var f : function (Double) =

function (a : Double) {

return a >= 0;

}

Declara uma variável cujo tipo é uma função com um parâmetro Double e tipo de retorno Boolean, e inicializa-a com uma função deste tipo

var f =

function (a : Double) {

return a >= 0;

}

Declara uma variável cujo tipo é uma função com um parâmetro Double e tipo de retorno Boolean, e inicializa-a com uma função deste tipo

function (a : Double) { a = 5 }

Ilegal: é proibido modificar parâmetros

Tabela 3. Declarações de variáveis e funções.

Na Tabela 3, podemos ver que as variáveis de JavaFX podem ser declaradas com var ou def. A diferença é que o def é usado para constantes (similar às variáveis final do Java), enquanto var indica variáveis propriamente ditas (i.e., cujo valor pode variar).

As declarações estáticas de tipo são opcionais, mas isso não significa que JavaFX Script seja uma linguagem dinamicamente tipada. Ao contrário, JavaFX é tão estaticamente tipada quanto Java; a diferença é que contamos com inferência de tipos, ou seja, a capacidade do compilador de determinar automaticamente o tipo. No exemplo var x = 5, o tipo de x é estabelecido como Integer, pois este é o tipo do valor 5 com o qual a variável é inicializada. No caso específico das declarações def, como o valor inicial é mandatório, a declaração de tipo jamais é necessária.

Também salta aos olhos que JavaFX Script utiliza o “estilo Pascal” de declarações de tipos, ou seja nome : Tipo ao invés de Tipo nome como em Java. Apesar de parecer uma diferença gratuita e desagradável de Java, você se acostumará e verá que existem bons motivos para isso (o mesmo vale para Scala, que – não coincidentemente – também usa inferência de tipos).

A mesma sintaxe de declarações pode ser utilizada em três escopos distintos:

var x = 5;
  function x1 () { return x; } // Retorna 5
  class Y {
    var x = 6;
    function x2 () { return x; } // Retorna 6
    function z () {
      var x = 7;
      def f = function x3 () { return x; } // Retorna 7
    }
  }

JavaFX suporta variáveis e funções locais, de instância, e globais. Esta última opção parece ser uma novidade, mas de fato não é, pois ocupa o lugar dos static do Java. Se num arquivo Y.fx você tiver um elemento global x, este será acessível externamente com a sintaxe Y.x, ou seja, igual às statics. Em termos de compilação e desempenho, a equivalência também permanece: variáveis e funções globais são transformadas pelo javafxc em variáveis e métodos static.

A inferência de tipos também funciona para funções, especialmente o tipo de retorno: raramente é preciso especificá-lo explicitamente, pois ao encontrar por exemplo uma operação return a + b no corpo da função, o javafxc determina que o tipo desta expressão é o tipo estático de retorno a função. Mas alguns programadores podem preferir especificar o tipo de retorno, para ter certeza que este será um tipo específico (neste caso, um return e onde e não possui exatamente o mesmo tipo declarado para retorno, sofrerá uma conversão implícita se possível).

O compilador javafxc irá transformar as funções em métodos idênticos aos do Java, com uma “assinatura” no bytecode que inclui os tipos dos parâmetros e do retorno, sendo que esta assinatura influi na compilação e na compatibilidade binária de outras classes compiladas com invocações ao método (função) em questão. Assim, as funções de JavaFX Script podem ser também invocadas a partir de classes Java, de maneira direta e tipicamente sem nenhum overhead.

Os tipos dos parâmetros, surpreendentemente, também podem ser inferidos. Isso é possível se houver pelo menos uma invocação à função no mesmo script, ou em atribuições ou inicializações para uma propriedade tipada como função:

function teste (a, b) { a + b }
  println(teste(10, 15));
   
  class A { var b : function (a:String); }
  A { b: function (x) { println(x.charAt(0)) } }

No primeiro exemplo, o compilador determina que os parâmetros a e b de teste() são do tipo Number, devido à presença da invocação teste(10, 15). No segundo exemplo, ao compilar a última linha onde instanciamos um objeto do tipo A, a function (x) {...} não exige declarar que x é uma String pois esta função está sendo atribuída à propriedade A.b, e para esta atribuição ser válida, a função no lado direito deve ter a mesma assinatura da propriedade no lado esquerdo.

Visibilidade

JavaFX suporta um conjunto completo de modificadores de visibilidade, de fato, mais completo que Java. Veja a relação completa na Tabela 4. Mais explicações (sobre a necessidade dos novos níveis) na próxima seção. Mas antes de chegar lá, podemos destacar um fato interessante: JavaFX não possui um nível de visibilidade private. Você logo entenderá o motivo.

VISIBILIDADE

Modificador

Significado

public

Igual a Java, permite acesso a partir de qualquer código

protected

Igual a Java, permite acesso para a mesma classe ou derivadas, e também outras classes do mesmo package

package

Igual ao package-private do Java (exceto por exigir uma palavra-chave: não é a visibilidade default!), permite acesso para classes do mesmo package

public-init

(Somente para propriedades) Permite inicialização em cláusulas de construção do objeto; depois disso, comporta-se como script-private

public-read

(Somente para propriedades) Permite inicialização e também leitura de qualquer código, mas não permite outras alterações exceto para código do mesmo script

script-private

Sem palavra-chave, é a default: permite acesso total para código do mesmo script (seja qual for a classe, ou mesmo código “global” do script). Comparando com Java, é um nível intermediário entre private e package-private

Tabela 4. Níveis de visibilidade.

Propriedades

JavaFX Script utiliza o termo “propriedade” ao invés de atributo. Propriedades são atributos de classes, mas com alguns recursos adicionais em comparação com os atributos do Java.

PROPRIEDADES

Declaração

Significado

protected var nome : String;

Assim como em Java, o modificador de visibilidade (caso exista) vem no início da declaração, antes do var/def

var nome : String

on replace {

if (not isInitialized(nome)

println("Novo nome: {nome}");

}

Trigger de alteração. Será invocado inclusive para a atribuição inicial à variável; se for necessário discriminar esta situação, basta usar isInitialized()

var nome : String

on replace nomeVelho {

println("Velho: {nomeVelho},

novo: {nome}");

}

Trigger de alteração, com cláusula de acesso ao valor anterior da propriedade

var projetos : Projeto[]

on replace pVelhos[i .. j] = pNovos {

println("Alterando: {pVelhos[i..j]}

para: {pNovos}");

}

Trigger de alteração, com cláusula de acesso ao valor anterior, para sequence. Neste caso temos até quatro variáveis para controle da alteração: pVelhos[i .. j] é a parte da sequence que foi modificada, pNovos contém os novos elementos que substituíram aquela parte ([] para um delete).

var osvaldo = Pessoa {

nome: "Osvaldo",

cargos: [

Cargo { titulo: "Editor" },

Cargo { titulo: "Desenvolvedor" }

]

}

Instanciação e inicialização de objetos (ou árvores de objetos), através da enumeração de valores iniciais para suas propriedades públicas.

É também a notação de objetos literais de JavaFX Script (não por acaso, idêntica à JSON de JavaScript tipado).

Tabela 5. Propriedades.

A Tabela 5 resume a sintaxe, que complementamos com as seguintes explicações:

Não se usa getters ou setters. Propriedades são declaradas com visibilidade pública. Para propriedades que você não deseja que possam ser modificadas arbitrariamente, basta usar os níveis public-init (equivalente a um atributo do Java que só pode ser inicializado por um construtor) ou public-read (equivalente a um atributo do Java que possui getter, mas não setter);

Não há construtores. Objetos são instanciados com uma sintaxe de objetos literais;

Triggers. As propriedades podem possuir triggers, que são funções invocadas automaticamente quando o valor da função for modificado. Isso substitui a necessidade de setters para alterações não-triviais de propriedades;

Binding. O recurso de binding (ver seção Binding) permite manter diversas propriedades associadas entre si de forma automática.

Para quem vem de uma linguagem OO tradicional como C++ ou Java, poderão parecer “heréticas” idéias como não encapsular atributos com getters e setters, ou não dispor de um nível de visibilidade private. Mas analisando a linguagem como um todo, podemos entender seu design. A soma de recursos como visibilidade mais refinada (especialmente public-init e public-read), instanciação “estilo JSON”, triggers e binding, substitui 99% das necessidades de getters, setters e construtores. Ou seja, seus atributos (opa, propriedades!) continuarão tão bem encapsulados quanto antes, só que com um código bem mais simples – e, de quebra, algumas novas facilidades.

Quanto à visibilidade private, esta não existe de fato nem em Java – um membro private de uma classe pode ser acessado por suas classes aninhadas ou inner classes. Classes aninhadas/inner costumam ser fortemente acopladas à sua enclosing class, justificando o acesso privilegiado. Em JavaFX a lógica é igual: todo o código contido por um script .fx é fortemente acoplado (se não for, divida-o em vários scripts). JavaFX só torna explícito e homogêneo um design que, em Java, é acidental e imperfeito. Acidental, pois não era assim no Java 1.0 – mudou no 1.1 quando as classes aninhadas/inner foram criadas. Imperfeito, pois Java permite definir várias classes no mesmo arquivo .java (desde que só uma seja pública), mas não há privilégios de acesso a membros privados entre estas classes – embora o mesmo argumento de alto acoplamento seja válido.

Ilustrando de forma mais aplicada as sintaxes de propriedades e construção de objetos, apresento novamente o “Alô Mundo” da JavaFX:

Stage {

    title: "Aplicação Alô Mundo"
    width: 250 height: 80
    scene: Scene {
      content: Text {
        font: Font { size: 24 }
        x: 10, y: 30
        content: "Alô, JavaFX!"
      }
    }
    onClose: function() { FX.exit() }
  }

O código acima constrói a aplicação completa (ok, faltando só as declarações package e import). Este código instancia (e retorna – ver seção Controle) um objeto Stage. Este objeto é inicializado com o valor 250 para a propriedade width, etc. No caso da propriedade scene, seu valor é um objeto Scene que é inicializado da mesma forma, idem para a propriedade content de Scene. Finalmente, a propriedade onClose tem o tipo function (): Void, sendo inicializada com uma função deste tipo.

Teoricamente, você poderia fazer o mesmo em Java, com expressões new aninhadas. Mas há três problemas. Primeiro, os construtores do Java são rígidos – se um objeto com os atributos a, b, c possui apenas um construtor (a,b), inicializar apenas a exige passar explicitamente o valor default de b, e inicializar c exige invocar setC() após a construção (saindo além da inicialização hierárquica). O segundo problema é que os frameworks Java não costumam trabalhar com “atributos funcionais” como onClose, preferindo design patterns como Template Method (um método onClose() que pode ser redefinido) ou Observer (um método como addCloseListener()); ambos suportam uma expressão única de inicialização, mas ao custo de código bem mais confuso devido à horrível sintaxe das inner classes. Terceiro, a invocação de construtores não indica o nome dos argumentos, de forma que expressões de construção longas e/ou aninhadas tendem a ficar difíceis de ler se você não souber de cor qual argumento que vai em cada posição (ou se isso não for evidente pelos valores passados).

Alguns frameworks Java modernos adotam o pattern de “APIs fluentes” no qual métodos terminam com um return this, o que permite encadear expressões como: new X(a).setB(b).setC(c), etc. Um método addXxxListener() também poderia seguir este design. Mas o resultado, mais uma vez, é bem pouco elegante. Na minha opinião, é uma tentativa pobre de imitar a linguagem Smalltak, na qual o estilo “fluente” é natural devido à sua sintaxe de mensagens (não há parâmetros posicionais) e ao fato de todos os métodos retornarem this por default (não existe o tipo void). E como, na definição do Java SE 7, os conservadores venceram a briga e ficaremos sem closures, o Java continua condenado a gambiarras pavorosas (ainda que sejam melhor-que-nada) como “APIs fluentes”.

Por que tudo isso é importante – não é muito oba-oba em cima de uma economia de algumas linhas de código para inicializar objetos complexos? Acontece que esta facilidade sintática possui outros “efeitos colaterais” positivos. Por exemplo, você pode usar a sintaxe de notação de objetos literais do JavaFX para muitas tarefas onde tipicamente usaria XML, resultando num código que é mais enxuto e legível, fácil de emitir e interpretar (como sabe qualquer programador JavaScript que prefere JSON a XML). Um exemplo concreto disso é o formato gráfico FXD (ver Edição 67), nada mais que código JavaFX com inicializações de classes gráficas. Outro exemplo é a construção da árvore de componentes e eventos da GUI, ilustrada acima: este código, além de enxuto e legível, é ideal para manipulação de ferramentas. Por tudo isso, JavaFX dispensa a necessidade de linguagens de descrição de GUI como XAML (Silverlight), MXML (Flex), XUL (Mozilla), “layouts” do Android, etc. A falta de algo equivalente (uma DSL para GUI & gráficos) não é uma lacuna de JavaFX, pelo contrário, sua presença em plataformas competidoras revela inadequações das linguagens de programação principais destas plataformas. Na JavaFX, o FXD é inclusive bom o suficiente para substituir o formato SVG de gráficos vetoriais.

Binding

Binding é uma das características mais “famosas” de JavaFX Script, pois além de ser um poderoso recurso de programação em geral, é uma peça essencial para os frameworks e o estilo de programação da plataforma JavaFX.

BINDING

Declaração

Significado

def x = bind y

Sempre que o valor de y mudar, este mesmo valor será copiado para x (x é um alias para y)

def x = bind y + f(10)

Sempre que o valor de y mudar, a expressão y + f(10) é reavaliada e o novo resultado atribuído a x. Note que f(10) não será invocada novamente – o valor retornado anteriormente será reutilizado

def x = bind y + f(z, 10)

Dessa vez, como a invocação a f() tem um parâmetro alimentado por uma variável z; se esta variável mudar, f(z, 10) será reavaliada

bound function f (x : Double) {

return x * y

}

def z = bind f(a)

Como f é declarada como uma bound function, o bind de z reexecutará f(a) sempre que qualquer variável utilizada por f() seja alterada. Isso inclui tanto os parâmetros (como a) quanto outras variáveis externas (atributos ou globais) como y

def iniciaisDias = bind

for (d in dias) d.charAt(0)

Sempre que ocorrer uma modificação na sequence dias, o for será reexecutado somente para os elementos modificados, resultando em alterações também incrementais na sequence iniciaisDias

def posAtual = bind Point {

x: xAtual, y:yAtual

}

Se uma das variáveis xAtual ou yAtual mudar, será construído um novo objeto Point, que será atribuído a posAtual

def posAtual = Point {

bind x: xAtual, y:yAtual

}

Se a variável xAtual mudar, a propriedade posAtual.x será modificada (não será criado um novo objeto Point). Se yAtual for alterada nada acontecerá, pois não tem bind

def x = bind y with inverse

Se y for alterada, seu valor é copiado para x.

Mas se x sofrer uma atribuição, seu valor é copiado para y

Nota: with inverse não suporta expressões mais complexas

Tabela 6. Binding.

Se você (como eu) começou aprendendo sobre binding examinando os fontes de diversos demos que só utilizam as duas variantes mais simples deste recurso (bind x = y e criação de objetos com binding igualmente simples em propriedades), poderá se surpreender com a Tabela 6, que mostra uma sofisticação extraordinária. Vamos demonstrar esta capacidade com um exemplo que explora uma das sintaxes complexas, combinado com triggers para logar o que acontece:


  var dias = [ "Dom", "Seg", "Ter" ]
    on replace d[i..j] = nd { println("{d[i..j]} = {nd}") }
   
  def iniciaisDias = bind for (d in dias) d.charAt(0);
   
  dias[1..2] = [ "TER", "QUA" ];

Executando o código acima, o output será o seguinte:

 -> DomSegTer
   -> DST
  SegTer -> TERQUA
  DST -> DTT
  DTT -> DTQ

As duas primeiras linhas são efeito das atribuições dos valores iniciais a dias, e por conseqüência do binding, a iniciaisDias – pois a trigger é disparada inclusive para a atribuição inicial. (Note que ao exibir sequences com o recurso de mesclagem de strings, os elementos são concatenados.) Na terceira linha, que é o que mais nos interessa, “SegTer -> TERQUA” vemos que o binding de iniciaisDias foi acionado pela atribuição a dias; sendo que esta atribuição modificou apenas dois dos seus elementos. Na quarta e quinta linhas, vemos que o for (d in dias) é reexecutado somente para estes elementos (dias[1..2]), e as alterações de iniciaisDias também são feitas de maneira incremental: repare que são geradas duas alterações independentes para os elementos de índice 1 ("S"®"T") e 2 ("T"®"Q"), ao invés de uma alteração única que causaria um log "DST -> DTQ".

Podemos concluir várias coisas. Primeiro, como já disse, o binding do Java vai bem além daqueles casos simplórios que você viu em javafx.com/samples; é um recurso que pode ser explorado de inúmeras formas. Segundo, as capacidades das sequences do Java são ainda mais incrementadas pelo tratamento especial de outros recursos da linguagem, como triggers e binding – de uma forma geral, a linguagem tenta otimizar o esforço destas operações tornando-as o mais incrementais possíveis (se você tem uma sequence de 1000 elementos e altera apenas um elemento, isso não irá gerar execuções de triggers ou bindings considerando todos os 1000 elementos).

Como nada é perfeito, um alerta: binding é pesado, especialmente devido às capacidades de avaliação incremental de sequences e expressões complexas. Para cada variável sujeita a binding e cada expressão bind, o runtime é obrigado a manter na memória estruturas de dados relativamente grandes. É por isso que você pode encontrar na internet algumas pessoas comentando que ficaram horrorizadas ao ver que uma variável Boolean ocupava 100 bytes ou mais. Use este recurso apenas quando necessário (especialmente na JavaFX Mobile).

Operadores

Já apresentamos alguns operadores da JavaFX especializados em sequences; veremos agora os operadores mais convencionais para manipulação de valores em geral.

OPERADORES ARITMÉTICOS

Operador

Significado

+ - * / ++ --

Igual a Java

+= -= *= /=

Igual a Java: operadores com atribuição combinada

mod

Resto de divisão, como o % do Java (não há equivalente a %=)

OPERADORES RELACIONAIS

Operador

Significado

not

Negação, como o ! do Java

or

Disjunção, como o || do Java

and

Conjunção, como o && do Java

== != <= >= < >

Igual a Java

OUTROS OPERADORES

Operador

Significado

instanceof

Igual a Java

obj as String

Typecast: como (String)obj do Java

sizeof e

Retorna o tamanho de uma expressão. Para uma sequence, é o seu número de elementos. Para objetos de outros tipos (ex.: Integer), é 0 se o objeto for null, 1 se não-nulo.

Tabela 7. Operadores.

A Tabela 7 revela algumas das minhas poucas discordâncias do design de JavaFX Script. Alguns operadores desviam de Java, a meu ver gratuitamente: por que, por exemplo, and ao invés de &&? O argumento é que operadores nomeados são mais “simples” que símbolos misteriosos como &&, mas não engulo isso, lembrando que linguagens RIA competidoras, como JavaScript e ActionScript, utilizam os operadores simbólicos tradicionais da família C. Os operadores simbólicos são mais legíveis pois se destacam de nomes de variáveis e funções; ao ler um código como aa || bb && cc, a distinção entre operadores/operandos é imediatamente clara, o que não ocorre para aa or bb and cc – que exige um “parsing mental” para reconhecer or e and como operadores e as demais palavras como identificadores de variáveis. Pior que isso, vejo algumas inconsistências: a exclamação ! foi substituída por not como operador de negação, mas no operador diferente-de !=, continuamos vendo a exclamação indicando negação. Mais: o ‘|’ de [a | a > 5] e o where de for (a in b where a > 5) têm exatamente o mesmo significado... nada é perfeito, e na minha opinião, a sintaxe de operadores não-simbólicos é um lugar onde o design de JavaFX Script derrapou.

Outras divergências me parecem OK. O operador de typecast as é mais elegante que o do Java, pois sendo posfixado, é coerente com a ordem de avaliação, ex.: dVar * 2.0 as Integer, temos primeiro uma multiplicação entre variáveis Double, depois sua conversão para Integer, e observe também que não precisamos usar parênteses – (dVar * 2.0) as Integer – pois a precedência do as é mais fraca (i.e., o typecast é avaliado depois da maioria dos outros operadores). E o sizeof é um conceito bastante interessante, coerente com o design orientado a expressões de JavaFX Script.

JavaFX não possui operadores de manipulação de bits, como |, &, ^, ~, <<, >> e >>> do Java. O veterano de C/C++/Java dentro de mim também não gostou disso à primeira vista, mas acabei entendendo, pois estes operadores são de uso muito raro na enorme maioria das aplicações, e JavaFX Script é uma linguagem dedicada à camada de aplicação – não se supõe que alguém vá usá-la para “escovações de bits” como algoritmos de compressão ou criptografia. Principalmente lembrando que JavaFX é uma linguagem “parceira” de Java: é trivial invocar métodos de casses Java a partir de JavaFX e vice-versa, misturar ambas classes no mesmo projeto/IDE etc.

Além disso, JavaFX reserva os tokens << e >> para outro propósito: escape de literais externas. Por exemplo, se você tiver que invocar um método bind() de uma classe Java, pode fazê-lo com obj.<<bind>>(argumentos). Sem o escape isso seria ilegal por que bind é uma palavra-chave em JavaFX. Java 7 também terá um mecanismo de escape similar,

Controle

JavaFX Script, como qualquer linguagem de programação, precisa de estruturas de controle como decisões e loops. Você poderia imaginar que pelo menos nesta área, a linguagem seria praticamente igual a Java. Engano, também aqui JavaFX apresenta inovações poderosas, muito embora mantenha uma boa similaridade sintática com Java. Veja a Tabela 8.

DECISÃO

Declaração

Significado

if (1 != 0) then

println("ok!")

else

println("pane na CPU!")

Igual a Java, exceto que a palavra-chave then é suportada

(porém, opcional)

var msg =

if (1 != 0) "ok" else "pane"

Igual ao operador ternário ‘?:’ do Java

msg = "ok"

ITERAÇÃO / GENERATORS

Declaração

Significado

while (x++ < y) println(x)

Igual a Java. A expressão while tem tipo Void

for (d in dias) println(d)

Igual ao for estendido do Java 5, só muda ‘:’ para ‘in’

var nums = for (n in [3, 2, 1]) n*2

Gera uma sequence com todos os valores “retornados”

nums = [ 6, 4, 2 ]

for (n in nums where n >= 0)...

Filtra a iteração, ignorando elementos que não satisfazem

ao where. Em Java, exigiria um for contendo um if

for (d in dias) println(indexof d)

indexof var retorna o número ordinal do elemento iterado

(0, 1, 2, ...). Tecnicamente, indexof é um operador de sequences, pois a iteração é sempre feita sobre sequences

for/while (..) {

if (a) break else continue

}

Como em Java, sendo que break e continue têm tipo Void

Não há “gotos disfarçados” break label ou continue label

EXCEÇÕES

Declaração

Significado

try, throw, catch, finally, throws

Como em Java. As expressões try e throw têm tipo Void

BLOCOS

Declaração

Significado

{ fazAlgo() }

{ fazAlgo(); return }

Igual a Java: bloco

{ fazAlgo(); return valor }

{ fazAlgo(); valor }

Igual a Java: bloco, retornando valor.

Note que o return é opcional

INVOCAÇÃO

Declaração

Significado

new Cliente("Osvaldo")

Igual a Java: instancia objeto e invoca construtor

m(a, b)

obj.m(a, b)

Igual a Java: invocação de método,

sendo this o receiver default

Tabela 8. Estruturas de controle.

Começaremos pela novidade conceitual: JavaFX Script é uma linguagem orientada a expressões. Não existe a dualidade entre “statements” (que não geram nenhum valor, como o if do Java) e “expressões” (que retornam um valor, como x + y). Porém, nos casos onde não há nenhum valor que faça sentido associar a determinada expressão, seu tipo é definido como Void.

Isso pode parecer malandragem, qual é a diferença entre um statement e uma expressão Void? A maior diferença é técnica: a gramática da linguagem fica mais simples e unificada, o único efeito do Void é não permitir o uso de determinada expressão no lado direito de lugares que exijam um valor, como uma atribuição. Porém, podemos ver que a linguagem faz um grande esforço para que quase tudo seja uma expressão normal (não-Void). Notavelmente, as estruturas de controle if e for são expressões que retornam valores. Vemos também que num bloco de código, o return é geralmente desnecessário para retornos com valor – basta terminar o bloco com um valor, como:

def function par (n : Number) { n mod 2 == 0 }

O bloco {} em si é uma expressão que possui valor, o qual pode ter tipo Void (se termina por um return sem valor ou outra expressão de tipo Void) ou outro tipo qualquer.

E não, a Tabela 8 não está incompleta: Veja o quadro “Cadê o switch?”.

Cadê o switch?

Se o leitor viu a Tabela 8 com as estruturas de controle de JavaFX e estiver perguntando “onde está o switch?”, a resposta simples é: não tem. Mas a resposta mais longa é... tem algo parecido, ou poderá ter logo.

Muitos designers de linguagens OO apontam que a estrutura de controle switch é anti-OO pois muitas vezes pode ser substituída por polimorfismo ou outras técnicas. Em JavaFX, tiveram a coragem de deixar o switch de fora. Isso significa que você tem que usar ou polirmorfismo, ou cascatas de ifs, onde normalmente usaria um switch.

Mas esta parte do design da linguagem me parece incipiente pois, para realmente não sentirmos falta do switch, precisamos de outros mecanismos (o polimorfismo nem sempre é adequado). JavaFX já possui parte da solução (funções de primeira ordem) – é melhor ilustrar com um exemplo prático:

[
    function() { println("Case 0"); }
    function() { println("Case 1"); }
    function() { println("Case 2"); }
    function() { println("Case 3"); }
  ][valor]();

Ao invés de case, usamos uma sequence contendo uma função com o código correspondente para o case do seu índice. Ao invés de switch(valor), usamos o valor para indexar a sequence, obtendo e executando a função que contém o código deste “caso”. Note que os “casos” poderiam ter parâmetros e retornar valores (desde que todos tenham a mesma assinatura: isso será verificado pelo compilador!), o que já tornaria esta técnica mais poderosa que o switch/case do Java.

Observe mais uma vez a inferência de tipos de JavaFX: no exemplo, o tipo da sequence é function() [].Digamos que você edite apenas um dos “casos” e coloque um parâmetro Integer na função; então o tipo inferido será o tipo mais elementar compatível com todos os casos – no caso, um tipo Function genérico (herdado por todos os tipos de função). Mas se você utilizar a sequence como no exemplo, indexando-a e executando a invocação com (), o compilador exige que todas as funções possuam assinatura compatível com a da invocação – no caso, nenhum parâmetro – e reclamará de qualquer “caso” que seja diferente disso.

O maior problema é que nem todos os switch/case possuem um domínio de valores como 0, 1, 2..., que se prestem ao mapeamento para índices de sequence. O ideal seria então usar mapas (sequences associativas), o que permitiria criar uma estrutura como [ "Ouros": function() { println("Ouros"} ], etc.; isso também permitiria o uso de objetos arbitrários, não só números, como chave. Infelizmente JavaFX ainda não possui nenhum suporte nativo a estruturas de dado deste tipo – pode-se usar as do Java como java.util.HashMap, mas isso não teria facilidades como uma sintaxe especializada de inicialização e indexação.

Uma versão futura da linguagem possuirá este suporte a mapas, provavelmente a JavaFX 2.0 (ver JFXC-642 no JIRA). Mas também seria bom permitir a declaração de funções triviais ainda mais simples (sem o function()), e mesmo assim, faltaria o default.

Há linguagens, como Smalltalk, que sempre se viraram muito bem sem o switch. Na pior hipótese você pode simplesmente usar ifs em cascata, o que em JavaFX talvez não seja ruim, lembrando que if é uma expressão e você pode escrever código como:

def sobrenome =
      if (nome == "Osvaldo")
          "Doederlein"
      else if (nome == "Eduardo")
           "Spínola"
      else
          "Silva";

…o que é mais enxuto que, com um switch, ter que fazer uma atribuição separada para sobrenome em cada case. (Para um exemplo tão trivial o Java permitiria usar o ternário ?:, mas não se os cases tivessem que executar algum statement antes do valor retornado.) Mas esta solução ainda incomoda um pouco: esteticamente, o primeiro ”caso” (if) não tem um cabeçalho idêntico aos demais (else if); e a repetição de "nome ==" também me incomoda um pouco, ainda que seja mais legível e genérica que os cases. Eficiência não é um problema pois o compilador poderia reconhecer uma cascata de ifs com estrutura similar a um switch, e gerar código idêntico (com bytecodes tableswitch ou lookupswitch).

Generators

Uma vantagem do design orientado a expressões de JavaFX, e seu uso de tipos de dados de alto nível como sequences e estruturas de controle avançadas como generators (for), é que o javafxc tem oportunidade para fazer otimizações importantes. Por exemplo, ao conhecer o for da JavaFX Script, você talvez tenha se horrorizado ao imaginar que este sempre irá gerar uma sequence com os valores produzidos por cada iteração do loop. Mas não é exatamente assim; o compilador só faz isso se necessário. Assim, no código

def x = [1, 2, 3]
  def y = for (n in x) n * 2

…a sequence será de fato gerada, no caso [2, 4, 6], e atribuída a y. Porém, neste outro código:

def x = [1, 2, 3]
  for (n in x) n * 2

…o for só irá avaliar a expressão n * 2 para cada n in x, mas nenhuma sequence será criada, pois o “valor de retorno” do for não está sendo utilizado de qualquer forma (como termo de outra expressão, lado-direito de atribuição, etc.).

Classes

JavaFX é uma linguagem OO baseada em classes, com sintaxe básica parecida com Java, mas com pelo menos um grande desvio do design de Java.

CONTROLE

Declaração

Significado

class Pessoa {

var nome : String;

var sobrenome : String;

function nomeCompleto () {

"{nome}{sobrenome}"

}

}

Declara uma classe

def craque = Pessoa {

nome: "Mané"

sobrenome: "Garrincha"

}

Instancia (aloca e inicializa) objeto de uma classe

def Craque = new Pessoa();

craque.nome = "Mané"

craque.sobrenome = "Garrincha"

Instancia (aloca e inicializa) objeto de uma classe

(Classes JavaFX não podem ter construtores, mas a sintaxe

new é suportada por questão de homogeneidade)

def list = new java.util.ArrayList(100);

Instancia (aloca e inicializa) objeto de uma classe

Neste caso é uma classe Java, que tem construtores com

parâmetros, que podemos invocar de forma normal

abstract class IPessoa {

abstract function nomeCompleto ();

}

Classe abstrata pura (substitui interfaces)

class A extends B, C, D {...}

Herança

class C {

init { println("init ok!"); }

postinit { println("postinit ok!"); }

}

O bloco init é invocado após a alocação do objeto e a inicialização de todas as propriedades (inclusive de todas as classes-base). Os postinit são invocados depois disso, e se for o caso, depois que todos os init da hierarquia

class X {

var y = 0;

function f () { y + y }

}

class X extends Y {

override var y = 5 on replace {…}

override function f () { y * 3 }

}

Pode-se redefinir tanto funções quanto propriedades.

No caso das propriedades, o override serve apenas para modificar o valor default e/ou a cláusula on replace

Tabela 9. Classes.

A Tabela 9 resume a sintaxe das classes de JavaFX. Não existem interfaces, apenas classes, e a herança múltipla é suportada para tudo (funções abstratas e concretas, e até mesmo propriedades). Classes da JavaFX podem inclusive herdar qualquer coisa de Java (tanto classes quanto interfaces). Ou seja, JavaFX Script unifica nossas class e interface numa única entidade, também não exigindo distinguir entre extends e implements.

Como JavaFX faz isso? Se você observar os arquivos gerados pelo javafxc, verá que uma classe C da JavaFX gera um par de .class, que se você descompilar gerando arquivos .java (ou examinar com o javap), verá que correspondem a uma interface C.Intf e uma classe C que implementa esta interface. Junto com alguns outros truques de compilação, isso permite integrar o paradigma de herança múltipla generalizada da JavaFX Script ao modelo OO do bytecode / JVM, que suporta herança múltipla apenas para interfaces com métodos abstratos.

Por que uma nova linguagem?

Ao serem apresentados à JavaFX, muitos desenvolvedores têm a seguinte reação: aprovam as funcionalidades, o framework, mas... isso precisava de uma nova linguagem? Não daria para suportar todos os recursos da JavaFX com APIs orientadas à linguagem Java, ou talvez, uma sintaxe “Java estendida” (mantendo compatibilidade retroativa) ao invés de inventar outra linguagem separada?

A resposta curta: Sim, seria possível fazer tudo com Java ou com uma extensão de Java... porém, não seria uma boa idéia. Vamos explicar os porquês – a “resposta longa”.

Produtividade Competitiva

Todas as plataformas RIA competidoras utilizam alguma linguagem de programação considerada de “alta produtividade” – pelo menos segundo alguns critérios, como alta densidade de código (mais funcionalidade com menos linhas de código) e facilidade de prototipação. As competidoras incluem JavaScript, ActionScript, e linguagens XML como XAML e MXML (para estas últimas, a vantagem é o uso de ferramentas visuais).

JavaFX Script possui diversas características de alta produtividade, como: inferência de tipos, binding, sequences, inicialização declarativa de estruturas hierárquicas de objetos, sintaxes de alto nível para diversas necessidades de programação geral (desde o simples relaxamento na pontuação até o for “turbinado”), e sintaxes especiais para algumas APIs.

Agilidade

Quando se fala em evolução da linguagem Java, não podemos nos esquecer que a Sun não é dona do Java. A especificação da linguagem, bem como de todas APIs formais, é controlada pelo Java Community Process (JCP). Embora a Sun mantenha certas prerrogativas e um alto grau de influência no JCP, isso não inclui carta branca para fazer o que quiser com a plataforma. Em especial, a evolução de especificações preexistentes é sempre “torturante” pois precisa levar em conta os interesses de diversos players com voz no JCP, e possivelmente grande investimento na tecnologia em questão. A quebra de compatibilidade retroativa é virtualmente impossível.

A Sun chegou atrasada à briga do RIA. Se fosse seguir o caminho de estender a linguagem Java (mesmo que isso fosse tecnicamente uma boa idéia), ou criar a JavaFX sob os auspícios do JCP, o lançamento da JavaFX 1.0 levaria pelo menos o dobro do tempo, e a sua evolução até a JavaFX 2.0 levaria 3-4 anos ao invés de um ano. Isso liquidaria qualquer chance da JavaFX de competir. Órgãos de padrões como o JCP são excelentes para tecnologias já razoavelmente maduras, mas são quase sempre péssimos para criar coisas completamente novas – a inovação raramente acontece em comitês.

Imperativo ou Funcional?

Na minha mania de aprender novas linguagens mexendo em código dos outros, cheguei ao seguinte trecho de um demo (javafx.com/samples/SmokeParticles/):

var parts: Particle[];
      …
      function update() : Void {
          insert Particle {
              x : 84 y : 164 timer : 100 acc : bind acc
              vx : 0.3 * random.nextGaussian()
              vy : 0.3 * random.nextGaussian() - 1
          } into parts;
          var i = sizeof parts - 1;
          while( i >= 0 ) {
              parts[i].update();
              if( parts[i].isdead())
                  delete parts[i.intValue()];
              i--;
          }
      }

Seu funcionamento é o seguinte: primeiro um novo objeto Particle é criado e inserido ao final da sequence parts; depois, um loop varre todos os elementos que já estavam na sequence, invocando os métodos update() e isdead(), e deletando da sequence aqueles que estão “mortos”. Mas achei esse código feio, confuso, então resolvi reescrevê-lo:

function update() : Void {
          parts = [
              for (p in parts where not p.update()) p
              Particle {
                  x : 84 y : 164 timer : 100 acc : bind acc
                  vx : 0.3 * random.nextGaussian()
                  vy : 0.3 * random.nextGaussian() - 1
              }
          ]
      }

Esta nova versão faz uma substituição única da sequence antiga pela nova, a qual é criada da seguinte forma: primeiro temos um for que retorna todos os elementos da sequence anterior que continuam vivos (juntei a funcionalidade de isdead() a update()), e depois temos a nova Particle. O ‘[...]’ mais externo serve concatena a sequence gerada pelo for com o objeto final, sendo que graças ao flattening automático, o resultado será uma sequence simples com todos estes elementos.

A nova versão é característica do estilo funcional, evitando alterações repetitivas como insert e delete. Sobrou uma alteração de variável (parts = ...), mas é uma só, e mesmo esta atribuição só restou por que eu não quis reescrever o programa todo. É um código bem mais “limpo”, menor (10 linhas ao invés de 14) e mais simples (sua complexidade ciclomática – quantidade e aninhamento de estruturas de controle – é menor.)

Mais importante, a versão funcional é idêntica a uma descrição formal / matemática do algoritmo, que podemos enunciar como: “o novo conjunto de partículas é formado pelas partículas preexistentes que após o update() continuem vivas, mais uma partícula nova”. É por isso que tanta gente gosta de programação funcional: é o estilo de programação que permite traduzir, de forma direta, algoritmos especificados de forma matematicamente rigorosa (que é quase sempre a descrição mais enxuta e elegante possível).

Bem, agora as más notícias. Testando meu novo código, este funcionou, mas com um desempenho muito pior. O problema é que a sequence parts é amarrada, via binding, ao scene graph da animação do programa; porém, o binding de sequences não é otimizado para o cenário de substituição total de uma sequence (com uma atribuição explícita), apesar de ser otimizado para manipulações pontuais como insert e delete. A otimização em questão evita que o scene graph inteiro seja reconstruído em cada frame da animação, o que resulta em péssimo desempenho. Ou seja, não se trata de um bug/limitação de desempenho das sequences propriamente ditas, mas somente da combinação entre sequences e binding, ou talvez, do runtime do scene graph. Registrei um novo bug descrevendo o problema (javafx-jira.kenai.com/browse/JFXC-2911). É o tipo de coisa que, infelizmente, podemos esperar de um software complexo como a JavaFX que ainda é praticamente recém-lançado no momento em que escrevo. Mas não tira o mérito do estilo de programação funcional ou do design de sequences (o JIRA da JavaFX registra uma grande quantidade de bugs similares, a maioria corrigidos antes mesmo do release 1.0 – mas o trabalho obviamente ainda não terminou)

Conclusões

Este artigo encerra uma primeira “trilogia” de JavaFX, na qual cobrimos os aspectos principais desta nova plataforma: tecnologia RIA, mobilidade, linguagem de programação, pontos principais do framework. A partir deste ponto, minha expectativa é que um leitor que sabe programar Java e tem interesse pela plataforma JavaFX possa andar com seus próprios pés, chegando ao ponto de desenvolver aplicações RIA sofisticadas para desktop e dispositivos móveis.

Há duas partes da JavaFX que cobrimos de forma superficial: os recursos de mídia (ver Edição 67) e as APIs javafx.* (ver Edição 68). Estes temas renderiam, seguramente, pelo menos mais dois capítulos da série. Mesmo na linguagem JavaFX Script, coberta neste artigo, existem tópicos específicos que poderíamos explorar, como a integração com Java, ou o desempenho.

Mas preferi parar por aqui, por três motivos. Primeiro, a plataforma JavaFX ainda é muito recente e ainda é difícil avaliar o interesse dos leitores por uma cobertura tão contínua e abrangente. Segundo, o mundo não parou por causa da JavaFX, e há outros temas que pretendo cobrir nas próximas edições. Terceiro, a JavaFX ainda está em rápida evolução: no caso específico dos frameworks, não vou gastar nosso tempo (meu e dos leitores) com um “artigão” sobre os frameworks da JavaFX 1.1, quando sei que já em junho deste ano (quando nossa próxima Edição já estaria nas bancas) a Sun terá lançado a JavaFX 1.5, com muitas novidades especialmente nas APIs. Resumindo, espero que o leitor tenha apreciado estes primeiros artigos, tanto quanto eu apreciei escrevê-los... chegou a hora de fazer uma pausa, mas com planos de voltar à JavaFX daqui a algumas edições, com as baterias recarregadas.