Bem-vindos de volta, fãs do Scala. No mês passado, conversamos sobre o Twitter, o site de micro-blogging que atualmente está despertando um tremendo interesse entre as pessoas que se socializam na Internet, e como seu API baseado em XML-/REST fazem dele um campo interessante para a investigação e exploração pelos desenvolvedores. Para esta finalidade, começamos a desvendar a estrutura básica do "Scitter", uma biblioteca Scala para acessar o Twitter.
Nós temos diversas metas para o Scitter:
- Para tornar o acesso ao Twitter substancialmente mais fácil do que apenas abrir uma conexão HTTP e fazer o trabalho "manualmente"
- Para ser tão facilmente acessível aos clientes Java
- Para facilitar as simulações para testes
Não vamos necessariamente desvendar todo o Twitter API nesta seção, mas cobriremos alguns itens-chave, procurando facilitar para que outros terminem o trabalho quando esta biblioteca chegar a um repositório de controle de fonte pública.
A história até agora: Scitter 0.1
Vamos começar com um lembrete rápido de onde paramos:
Lista 1. Scitter v0.1
package com.tedneward.scitter
{
import org.apache.commons.httpclient._, auth._, methods._, params._
import scala.xml._
/**
* Status message type. This will typically be the most common message type
* sent back from Twitter (usually in some kind of collection form). Note
* that all optional elements in the Status type are represented by the
* Scala Option[T] type, since that's what it's there for.
*/
abstract class Status
{
/**
* Nested User type. This could be combined with the top-level User type,
* if we decide later that it's OK for this to have a boatload of optional
* elements, including the most-recently-posted status update (which is a
* tad circular).
*/
abstract class User
{
val id : Long
val name : String
val screenName : String
val description : String
val location : String
val profileImageUrl : String
val url : String
val protectedUpdates : Boolean
val followersCount : Int
}
/**
* Object wrapper for transforming (format) into User instances.
*/
object User
{
/*
def fromAtom(node : Node) : Status =
{
}
*/
/*
def fromRss(node : Node) : Status =
{
}
*/
def fromXml(node : Node) : User =
{
new User {
val id = (node \ "id").text.toLong
val name = (node \ "name").text
val screenName = (node \ "screen_name").text
val description = (node \ "description").text
val location = (node \ "location").text
val profileImageUrl = (node \ "profile_image_url").text
val url = (node \ "url").text
val protectedUpdates = (node \ "protected").text.toBoolean
val followersCount = (node \ "followers_count").text.toInt
}
}
}
val createdAt : String
val id : Long
val text : String
val source : String
val truncated : Boolean
val inReplyToStatusId : Option[Long]
val inReplyToUserId : Option[Long]
val favorited : Boolean
val user : User
}
/**
* Object wrapper for transforming (format) into Status instances.
*/
object Status
{
/*
def fromAtom(node : Node) : Status =
{
}
*/
/*
def fromRss(node : Node) : Status =
{
}
*/
def fromXml(node : Node) : Status =
{
new Status {
val createdAt = (node \ "created_at").text
val id = (node \ "id").text.toLong
val text = (node \ "text").text
val source = (node \ "source").text
val truncated = (node \ "truncated").text.toBoolean
val inReplyToStatusId =
if ((node \ "in_reply_to_status_id").text != "")
Some((node \"in_reply_to_status_id").text.toLong)
else
None
val inReplyToUserId =
if ((node \ "in_reply_to_user_id").text != "")
Some((node \"in_reply_to_user_id").text.toLong)
else
None
val favorited = (node \ "favorited").text.toBoolean
val user = User.fromXml((node \ "user")(0))
}
}
}
/**
* Object for consuming "non-specific" Twitter feeds, such as the public timeline.
* Use this to do non-authenticated requests of Twitter feeds.
*/
object Scitter
{
/**
* Ping the server to see if it's up and running.
*
* Twitter docs say:
* test
* Returns the string "ok" in the requested format with a 200 OK HTTP status code.
* URL: http://twitter.com/help/test.format
* Formats: xml, json
* Method(s): GET
*/
def test : Boolean =
{
val client = new HttpClient()
val method = new GetMethod("http://twitter.com/help/test.xml")
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
client.executeMethod(method)
val statusLine = method.getStatusLine()
statusLine.getStatusCode() == 200
}
/**
* Query the public timeline for the most recent statuses.
*
* Twitter docs say:
* public_timeline
* Returns the 20 most recent statuses from non-protected users who have set
* a custom user icon. Does not require authentication. Note that the
* public timeline is cached for 60 seconds so requesting it more often than
* that is a waste of resources.
* URL: http://twitter.com/statuses/public_timeline.format
* Formats: xml, json, rss, atom
* Method(s): GET
* API limit: Not applicable
* Returns: list of status elements
*/
def publicTimeline : List[Status] =
{
import scala.collection.mutable.ListBuffer
val client = new HttpClient()
val method = new GetMethod("http://twitter.com/statuses/public_timeline.xml")
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
client.executeMethod(method)
val statusLine = method.getStatusLine()
if (statusLine.getStatusCode() == 200)
{
val responseXML =
XML.loadString(method.getResponseBodyAsString())
val statusListBuffer = new ListBuffer[Status]
for (n <- (responseXML \\ "status").elements)
statusListBuffer += (Status.fromXml(n))
statusListBuffer.toList
}
else
{
Nil
}
}
}
/**
* Class for consuming "authenticated user" Twitter APIs. Each instance is
* thus "tied" to a particular authenticated user on Twitter, and will
* behave accordingly (according to the Twitter API documentation).
*/
class Scitter(username : String, password : String)
{
/**
* Verify the user credentials against Twitter.
*
* Twitter docs say:
* verify_credentials
* Returns an HTTP 200 OK response code and a representation of the
* requesting user if authentication was successful; returns a 401 status
* code and an error message if not. Use this method to test if supplied
* user credentials are valid.
* URL: http://twitter.com/account/verify_credentials.format
* Formats: xml, json
* Method(s): GET
*/
def verifyCredentials : Boolean =
{
val client = new HttpClient()
val method = new GetMethod("http://twitter.com/help/test.xml")
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
client.getParams().setAuthenticationPreemptive(true)
val creds = new UsernamePasswordCredentials(username, password)
client.getState().setCredentials(
new AuthScope("twitter.com", 80, AuthScope.ANY_REALM), creds)
client.executeMethod(method)
val statusLine = method.getStatusLine()
statusLine.getStatusCode() == 200
}
}
}
|
É um pouco longo, mas bastante fácil de destilar em alguns componentes básicos:
- Classes de casos
UsereStatusrepresentam os tipos básicos que o Twitter envia de volta a seus clientes em resposta a uma chamada API, completa com métodos para construir e extrair de Representações XML. - Um objeto singleton do Scitter gerencia estas operações, que não requerem usuários autenticados.
- Uma instância do Scitter (parametrizada por nome de usuário e senha) é usada para operações que requerem um usuário autenticado.
Até agora nos dois tipos de Scitter, cobrimos somente os APIs teste, verifyCredentials e public_timeline. Embora eles ajudem a verificar se a parte básica do acesso HTTP (usando a biblioteca Apache HttpClient) funciona e se nossa forma básica de converter as respostas XML em Status objetos funciona, agora não conseguimos fazer nem a consulta básica de linha de tempo pública "o que meus amigos estão dizendo", nem demos os passos básicos para evitar os tipos de problemas de "repetir-se" no código base, e ainda nem começamos a ver maneiras de facilitar a simulação do acesso à rede para testes.
É óbvio que temos um longo caminho à frente neste episódio.
A primeira coisa que me incomoda no código é que estou repetindo a sequência de operações para criar uma instância HttpClient, inicializá-la, parametrizá-la com os parâmetros de autenticação necessários e assim por diante, em cada um dos métodos do objeto e da classe Scitter. Quando há somente três métodos entre os dois, pode ser controlável, mas fica claro que não irá escalar, e existem muitos métodos ainda por fazer. Além disso, será realmente difícil retornar a esses métodos depois e introduzir algum tipo de simulação e/ou capacidade de teste local/offline. Então, vamos consertar isso.
O que estamos apresentando aqui não é realmente uma especificidade do Scala em si; é apenas o bom e simples tipo de raciocínio "Não Se Repita". Assim, vou começar com a abordagem básica orientada para OO: criar um método para ajudar a fazer o trabalho real:
Lista 2. Aplicando DRY ao código base
package com.tedneward.scitter
{
// ...
object Scitter
{
// ...
private[scitter] def execute(url : String) =
{
val client = new HttpClient()
val method = new GetMethod(url)
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
client.executeMethod(method)
(method.getStatusLine().getStatusCode(), method.getResponseBodyAsString())
}
}
}
|
Observe algumas coisas sobre isso: em primeiro lugar, estou retornando uma tupla do execute() método contendo o código do status e o corpo da resposta; esta é uma das partes poderosas de ter tuplas como uma parte integrada da linguagem, pois fica realmente fácil devolver o que de fato acaba sendo valores múltiplos de retorno de uma chamada simples do método. É claro que, no código Java, podemos fazer a mesma coisa criando um nível superior ou classe aninhada que contenha os elementos da tupla, mas isso exigirá uma quantidade enorme de código que é específico para este método particular. Ou poderíamos devolver um Map com String chaves e Object valores, mas aí perderíamos grandemente a segurança na digitação. As tuplas não são um recurso que altera o jogo, e sim apenas outro "item legal" que faz do Scala uma linguagem poderosa de usar.
Em razão de estar usando uma tupla, vou querer usar outra sintaxe do Scala para capturar ambos os resultados em variáveis locais, como esta versão reescrita do Scitter.test:
Lista 3. Esta é uma execução DRY?
package com.tedneward.scitter
{
// ...
object Scitter
{
/**
* Ping the server to see if it's up and running.
*
* Twitter docs say:
* test
* Returns the string "ok" in the requested format with a 200 OK HTTP status code.
* URL: http://twitter.com/help/test.format
* Formats: xml, json
* Method(s): GET
*/
def test : Boolean =
{
val (statusCode, statusBody) =
execute("http://twitter.com/statuses/public_timeline.xml")
statusCode == 200
}
}
}
|
De fato, eu poderia facilmente remover statusBody totalmente e substituí-lo por _ porque nunca utilizo o segundo parâmetro (test não tem corpo de retorno), mas vou precisar do corpo para as outras chamadas, por isso vou deixá-lo aqui com finalidades de demonstração.
Note como execute() não vaza nenhum dos detalhes que lidam com a comunicação HTTP real — isso é o básico da Encapsulação 101. Isso vai facilitar a substituição de execute() por outra implementação mais tarde (o que faremos depois) ou a potencial otimização do código com a reutilização de um objeto HttpClient simples em vez de reinstanciar um novo a cada vez.
Depois, observe como o execute() método está no Scitter objeto? Isso quer dizer que poderei usá-lo a partir das diversas instâncias Scitter (pelo menos agora eu posso, até que faça algo dentro execute() que me impeça de fazê-lo) — é por isso que marquei execute() como private[scitter], o que significa que tudo dentro do com.tedneward.scitter pacote pode vê-lo.
(A propósito, se você ainda não o fez, execute os testes para garantir que tudo ainda esteja funcionando bem. Vou assumir que você está fazendo isso enquanto vemos o código, por isso se eu me esquecer de mencionar, isso não quer dizer que você pode esquecer de fazê-lo.)
A propósito, apoiando a classe Scitter vai requerer um nome de usuário e senha para acesso autenticado, por isso vou criar somente uma versão sobrecarregada do execute() método que usa dois adicionais Strings como parâmetros:
Lista 4. Uma versão ainda mais DRY
package com.tedneward.scitter
{
// ...
object Scitter
{
// ...
private[scitter] def execute(url : String, username : String, password : String) =
{
val client = new HttpClient()
val method = new GetMethod(url)
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
client.getParams().setAuthenticationPreemptive(true)
client.getState().setCredentials(
new AuthScope("twitter.com", 80, AuthScope.ANY_REALM),
new UsernamePasswordCredentials(username, password))
client.executeMethod(method)
(method.getStatusLine().getStatusCode(), method.getResponseBodyAsString())
}
}
}
|
De fato, considerando que os dois execute()s fazem praticamente a mesma coisa módulo os bits de autenticação, podemos reescrever o primeiro execute() inteiramente nos termos do segundo, lembrando que o Scala exige que sejamos explícitos quanto ao tipo de retorno do sobrecarregado execute():
Lista 5. DRY Radical
package com.tedneward.scitter
{
// ...
object Scitter
{
// ...
private[scitter] def execute(url : String) : (Int, String) =
execute(url, "", "")
private[scitter] def execute(url : String, username : String, password : String) =
{
val client = new HttpClient()
val method = new GetMethod(url)
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
if ((username != "") && (password != ""))
{
client.getParams().setAuthenticationPreemptive(true)
client.getState().setCredentials(
new AuthScope("twitter.com", 80, AuthScope.ANY_REALM),
new UsernamePasswordCredentials(username, password))
}
client.executeMethod(method)
(method.getStatusLine().getStatusCode(), method.getResponseBodyAsString())
}
}
}
|
Até agora, tudo bem Executamos o DRY nas partes de comunicação do código Scitter, então vamos passar para o próximo item da lista: Vamos pegar uma lista dos tweets dos meus amigos.
O Twitter API afirma que a friends_timeline chamada API "retorna os 20 status mais recentes publicados pelo usuário autenticado e pelos amigos desse usuário." (Ele também ressalta, para os que usam o Twitter diretamente no site do Twitter na Web, que "Este é o equivalente de '/home' na Web.") Este é um requisito bastante básico para qualquer Twitter API, então vamos adicioná-lo à Scitter classe; ele é adicionado à classe e não ao objeto porque, como indicam os documentos, isso é feito em nome do usuário em autenticação, que eu decidi que pertence à Scitter classe e não ao Scitter objeto.
Porém, agora somos arremessados como uma bola curva: A chamada friends_timeline aceita uma lista de "parâmetros opcionais", incluindo since_id, max_id, count e page para controlar os resultados encontrados. Essa vai ser uma operação complicada porque o Scala não suporta originalmente a ideia de "parâmetros opcionais" da forma como outras linguagens (como Groovy, JRuby ou JavaScript) fazem, mas vamos lidar primeiro com as coisas simples — vamos criar um método friendsTimeline que execute somente a chamada normal, não-parametrizada:
Lista 6. "Diga-me que empresa tu manténs..."
package com.tedneward.scitter
{
class Scitter
{
def friendsTimeline : List[Status] =
{
val (statusCode, statusBody) =
Scitter.execute("http://twitter.com/statuses/friends_timeline.xml",
username, password)
if (statusCode == 200)
{
val responseXML = XML.loadString(statusBody)
val statusListBuffer = new ListBuffer[Status]
for (n <- (responseXML \\ "status").elements)
statusListBuffer += (Status.fromXml(n))
statusListBuffer.toList
}
else
{
Nil
}
}
}
}
|
Até agora, tudo bem; o método correspondente para testar isso se parece assim:
Lista 7. "... e te direi quem és." (Miguel de Cervantes)
package com.tedneward.scitter.test
{
class ScitterTests
{
// ...
@Test def scitterFriendsTimeline =
{
val scitter = new Scitter(testUser, testPassword)
val result = scitter.friendsTimeline
assertTrue(result.length > 0)
}
}
}
|
Perfeito. Parece o método publicTimeline() do objeto Scitter e se comporta quase exatamente da mesma maneira também.
Ainda temos o problema dos parâmetros opcionais. Em razão do Scala não ter parâmetros opcionais como recurso de linguagem, a única opção , como primeira ideia, seria criar uma grande quantidade de métodos friendsTimeline() sobrecarregados, tomando alguns parâmetros fatorialmente governados.
Felizmente, existe um jeito mais fácil de fazer isso combinando dois recursos de linguagem Scala (um dos quais ainda não mencionei) de uma maneira interessante — classes de casos e "parâmetros repetidos" (consulte a Lista 8):
Lista 8. "Como eu te amo? ..."
package com.tedneward.scitter
{
// ...
abstract class OptionalParam
case class Id(id : String) extends OptionalParam
case class UserId(id : Long) extends OptionalParam
case class Since(since_id : Long) extends OptionalParam
case class Max(max_id : Long) extends OptionalParam
case class Count(count : Int) extends OptionalParam
case class Page(page : Int) extends OptionalParam
class Scitter(username : String, password : String)
{
// ...
def friendsTimeline(options : OptionalParam*) : List[Status] =
{
val optionsStr =
new StringBuffer("http://twitter.com/statuses/friends_timeline.xml?")
for (option <- options)
{
option match
{
case Since(since_id) =>
optionsStr.append("since_id=" + since_id.toString() + "&")
case Max(max_id) =>
optionsStr.append("max_id=" + max_id.toString() + "&")
case Count(count) =>
optionsStr.append("count=" + count.toString() + "&")
case Page(page) =>
optionsStr.append("page=" + page.toString() + "&")
}
}
val (statusCode, statusBody) =
Scitter.execute(optionsStr.toString(), username, password)
if (statusCode == 200)
{
val responseXML = XML.loadString(statusBody)
val statusListBuffer = new ListBuffer[Status]
for (n <- (responseXML \\ "status").elements)
statusListBuffer += (Status.fromXml(n))
statusListBuffer.toList
}
else
{
Nil
}
}
}
}
|
Consulte o * tagging no final do parâmetro de opções? Isso indica que o parâmetro é na verdade uma sequência de parâmetros, muito parecida com a estruturavarargs do Java 5. Assim como com varargs, o número de parâmetros passados pode ser zero assim como tínhamos antes (embora iremos precisar voltar ao código de teste e adicionar um par de parênteses à friendsTimeline chamada agora, pois de outro modo o compilador não saberá se estamos tentando chamar o método sem nenhum parâmetro ou tentando usá-lo com finalidades de aplicação parcial ou algo semelhante); também podemos começar a passar estes tipos de caso, como a seguir:
Lista 9. "... Deixe-me contar as maneiras" (William Shakespeare)
package com.tedneward.scitter.test
{
class ScitterTests
{
// ...
@Test def scitterFriendsTimelineWithCount =
{
val scitter = new Scitter(testUser, testPassword)
val result = scitter.friendsTimeline(Count(5))
assertTrue(result.length == 5)
}
}
}
|
É claro que sempre existe a possibilidade de que um cliente sociopata possa passar em alguma sequência realmente bizarra de parâmetros, como friendsTimeline(Count(5), Count(6), Count(7)), mas nesse caso entregaremos a lista cegamente ao Twitter (e esperaremos que seu gerenciamento de erros seja forte o bastante para pegar somente o especificado). Claro que, se isso se tornar uma preocupação, não é difícil olhar a lista de parâmetros repetidos e pegar o último de cada tipo especificado antes de construir a URL enviada para o Twitter. Enquanto isso, previna-se.
Porém, isso realmente levanta uma questão interessante: Quão fácil é chamar este método com o código Java? Afinal, se uma das principais metas desta biblioteca é manter a compatibilidade com o código Java, então precisamos ter certeza de que o código Java não tenha que saltar muitos arcos para utilizá-lo.
Vamos começar lançando a classe Scitterem nosso bom amigo javap:
Lista 10. Ah, sim, código Java... Agora eu me lembro....
C:\>javap -classpath classes com.tedneward.scitter.Scitter
Compiled from "scitter.scala"
public class com.tedneward.scitter.Scitter extends java.lang.Object implements s
cala.ScalaObject{
public com.tedneward.scitter.Scitter(java.lang.String, java.lang.String);
public scala.List friendsTimeline(scala.Seq);
public boolean verifyCredentials();
public int $tag() throws java.rmi.RemoteException;
}
|
Opa! Duas coisas saltam à minha mente aqui como preocupações. Primeiro, friendsTimeline() toma um scala.Seq como um parâmetro (que é o recurso de parâmetros repetidos que acabamos de usar). Segundo, o método friendsTimeline(), assim como com o método publicTimeline() do objeto Scitter (execute o javap nele para fazer uma checagem dupla se não acreditar em mim) retorna um scala.List de elementos. Quão utilizáveis são estes dois tipos de código Java?
A forma mais fácil de descobrir isso é escrever um pequeno conjunto de testes JUnit em código Java em vez de Scala, e é o que vamos fazer. Embora possamos testar a construção da instância do Scitter e chamar seu métodoverifyCredentials(), estes não são particularmente úteis — lembre-se, não estamos aqui (neste caso) para verificar a correção da Scitter classe, mas para ver como é fácil usar o item do código Java. Para este fim, vamos pular direto para escrever um teste que pegará a "linha do tempo dos amigos" — em outras palavras, queremos instanciar uma instância do Scitter e chamar seu friendsTimeline() método sem parâmetros.
Fazer isso é um pouco mais complicado, graças ao scala.Seq parâmetro que precisamos passar em — scala.Seq é uma característica do Scala que significa que ele é mapeado para o JVM subjacente, e por isso não podemos apenas instanciar um diretamente. Podemos tentar o parâmetro clássico Java null, mas fazer isso lança uma exceção no tempo de execução. O que precisamos é de uma scala.Seq classe que possamos instanciar facilmente com o código Java.
Acontece que encontramos um exatamente no mesmo mutable.ListBuffer tipo que usamos dentro da própria implementação do Scitter.
Lista 11. E agora lembramos porque eu gosto do Scala....
package com.tedneward.scitter.test;
import org.junit.*;
import com.tedneward.scitter.*;
public class JavaScitterTests
{
public static final String testUser = "TESTUSER";
public static final String testPassword = "TESTPASSWORD";
@Test public void getFriendsStatuses()
{
Scitter scitter = new Scitter(testUser, testPassword);
if (scitter.verifyCredentials())
{
scala.List statuses =
scitter.friendsTimeline(new scala.collection.mutable.ListBuffer());
Assert.assertTrue(statuses.length() > 0);
}
else
Assert.assertTrue(false);
}
}
|
Usar o scala.List retornado não é problema porque podemos apenas tratá-lo como faríamos com qualquer outra Collection classe (embora sintamos falta das facilidades das Collections API porque os métodos da Lista baseados no Scala assumem que você estará interagindo com eles a partir do scala),, e então navegar pelos resultados não é tão difícil, mas lembra um pouco a "velha escola" do código Java (em torno de 1995):
Lista 12. 1995 chamado; eles querem o Vector de volta...
package com.tedneward.scitter.test;
import org.junit.*;
import com.tedneward.scitter.*;
public class JavaScitterTests
{
public static final String testUser = "TESTUSER";
public static final String testPassword = "TESTPASSWORD";
@Test public void getFriendsStatuses()
{
Scitter scitter = new Scitter(testUser, testPassword);
if (scitter.verifyCredentials())
{
scala.List statuses =
scitter.friendsTimeline(new scala.collection.mutable.ListBuffer());
Assert.assertTrue(statuses.length() > 0);
for (int i=0; i<statuses.length(); i++)
{
Status stat = (Status)statuses.apply(i);
System.out.println(stat.user().screenName() + " said " + stat.text());
}
}
else
Assert.assertTrue(false);
}
}
|
O que nos leva para a próxima parte, a de passar os parâmetros para dentro do friendsTimeline() método. Infelizmente, o tipo ListBuffer não toma uma coleção como parâmetro construtor, e por isso temos que construir uma lista de parâmetros e depois passar a coleção pela chamada do método;é tedioso, mas não excessivamente trabalhoso:
Lista 13. Posso voltar para o Scala agora, por favor?
package com.tedneward.scitter.test;
import org.junit.*;
import com.tedneward.scitter.*;
public class JavaScitterTests
{
public static final String testUser = "TESTUSER";
public static final String testPassword = "TESTPASSWORD";
// ...
@Test public void getFriendsStatusesWithCount()
{
Scitter scitter = new Scitter(testUser, testPassword);
if (scitter.verifyCredentials())
{
scala.collection.mutable.ListBuffer params =
new scala.collection.mutable.ListBuffer();
params.$plus$eq(new Count(5));
scala.List statuses = scitter.friendsTimeline(params);
Assert.assertTrue(statuses.length() > 0);
Assert.assertTrue(statuses.length() == 5);
for (int i=0; i<statuses.length(); i++)
{
Status stat = (Status)statuses.apply(i);
System.out.println(stat.user().screenName() + " said " + stat.text());
}
}
else
Assert.assertTrue(false);
}
}
|
Assim, embora a versão Java seja um pouco mais verborrágica que sua equivalente no Scala, até agora ainda é bastante direto chamar a biblioteca do Scitter a partir de qualquer cliente Java que queira utilizá-la. Excelente.
Fica claro que ainda temos um longo caminho a percorrer com o Scitter, mas está começando a realmente tomar forma e, até agora, está sendo muito bom. Conseguimos executar o DRY nas partes de comunicação da biblioteca do Scitter e incorporar os parâmetros opcionais que o Twitter pede em algumas chamadas API diferentes que ele oferece — e, até agora, os clientes Java não ficarão desanimados demais com a API que estamos apresentando. Reconheço que essa não é uma API tão limpa quanto a que o Scala pode consumir naturalmente, mas se os desenvolvedores de Java quiserem usar a biblioteca do Scitter, eles não precisam fazer muita coisa para começar.
A biblioteca Scitter ainda carrega uma qualidade de "objeto", mas estamos começando a ver alguns recursos mais funcionais do Scala abrindo caminho para o sistema. Ao continuarmos a construção da biblioteca, cada vez mais destes recursos começarão a se introduzir lentamente, em qualquer local em que ajudem a tornar o código mais conciso e claro. E é assim que deve ser.
Por enquanto, é hora de nos separarmos enquanto eu faço uma breve pausa. Quando eu retornar, acrescentarei à biblioteca algum suporte para testes offline e também a habilidade de atualizar o status de um usuário. Até lá, fãs do Scala, lembrem-se: É sempre melhor ser funcional do que disfuncional. (Desculpem-me. Eu adoro demais essa piada.)
Aprender
-
O guia do Scala para desenvolvedores de Java atarefados" (Ted Neward, developerWorks): Leia a série completa.
-
"Scala e XML" (developerWorks, Abril 2008) mostra como o Scala pode tornar o trabalho com o XML uma alegria.
-
Este wiki na estrutura lift do Scala, uma estrutura para escrever aplicativos Web, demonstra os famosos apoios da estrutura (Seaside, Rails, Django, Wicket, etc.). Veja aqui um tutorial introdutório.
- "Functional programming in the Java language" (Abhijit Belapurkar, developerWorks, Julho de 2004): Explica os benefícios e usos da programação funcional da perspectiva de um desenvolvedor de Java.
- "Scala by Example" (Martin Odersky, Dezembro de 2007): Uma introdução breve e focada em código ao scala (em PDF).
- Programming in Scala (Martin Odersky, Lex Spoon, e Bill Venners; Artima, Dezembro de 2007): A primeira introdução ao Scala em formato de livro.
-
Bjarne Stroustrup: C++ desenhado e implementado, que ele descreveu como "um C melhor".
- Java Puzzlers: Traps, Pitfalls and Corner Cases (Addison-Wesley Professional, Julho de 2005) revela as excentricidades da linguagem de programação Java através de problemas de programação interessantes e intrigantes.
- A zona de tecnologia Java developerWorks: Centenas de artigos sobre todos os aspectos da programação Java.
Obter produtos e tecnologias
-
O componente Jakarta (Apache) Commons HttpClient proporciona um pacote eficiente, atual e rico em recursos que implementa o lado cliente dos mais recentes padrões e recomendações HTTP. Você também pode querer os componentes Commons Logging e Commons Codec .
- Faça um download do Scala: Comece a aprendê-lo com esta série.
- SUnit: Parte da distribuição padrão do Scala, no pacote scala.testing .
Discutir
-
blogs developerWorks: Envolva-se na comunidade do developerWorks.
