Blog do Urubatan
msgbartop
Desenvolvedor, Palestrante, Escritor, Nerd Assumido e Pai do Marcus :D
msgbarbottom

08 Aug 11 Scala – Mais um pouco de OO, views, traits e classes internas

Mais um post muito atrazado, desta vez levei mais de 3 meses para continuar falando de scala, mas como comentei no post anterior foram 3 meses corridos, então voltando ao histórico, este é o nono post da série sobre Scala, já falamos de o básico da linguagem scala, logo depois Orientação a objetos em scala, um pouco sobre closures, depois compreensão de listas e mapas, depois Programação Funcional em Scala, depois tipos parametrizados, depois pattern matching e case classes e por último sobre Atores e concorrência em scala. Agora vamos revisar alguns conceitos de orientação a objetos e vamos ver alguns recursos interessantes da linguagem scala.


Como vimos nos dois primeiros artigos, tudo em scala é um objeto, e scala suporta herança e polimorfismo, como podemos ver no exemplo abaixo (já mostrado antes):
samples/009_heranca.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Animal {
  def breathe : Unit;
}
class Dog extends Animal {
  def breathe {
    println("Dogs breath!");
  }
}
class Person(val fullName : String, val age : Int) extends Animal {
  def breathe = println("Dogs breath!");
  override def toString = "[Person name:" + fullName + "]"
}
class Student(fullName : String, age : Int) extends Person(fullName, age) {
}

Mas os recursos OO de scala não ficam só nisto, podemos utilizar classes internas como no exemplo simples abaixo:
samples/044_inner_class.scala

1
2
3
4
5
6
7
8
9
10
11
12
class Book {
  class Chapter(name:String,content:String) {
    override def toString() = "<chapter name=\"" + name + "\">" + content + "</chapter>"
  }
  var chapters: List[Chapter] = Nil
  def newChapter(name:String, content:String) = {
    val chap = new Chapter(name,content)
    chapters = chap :: chapters
    chap
  }
  override def toString() = "<book>" + chapters.foldRight("")((memo,it) => memo + it.toString ) + "</book>"
}

Este exemplo é bem simples para que não fiquem dúvidas, é possível criar classes aninhadas, mas diferente do Java, a classe Chapter não esta presa a classe Book, e sim a instância de livro que a criou, parecido com a relação entre os objetos de uma classe e o objeto da classe exterior em Java, podemos ver uma continuação do exemplo anterior para demonstrar melhor este conceito:
samples/045_inner_class.scala

1
2
3
4
5
6
7
8
9
val b = new Book
var c1 : b.Chapter = b.newChapter("Preface","""
This is a really beautiful book
With only one chapter
""")
var c2 : b.Chapter = b.newChapter("Other Title","""
Just kidding
no book can have only one chapter
""")

Mas ainda continuamos com exemplos simples. Em scala eu tenho herança e polimorfismo, e posso usar isto em métodos, métodos que mudam o tipo de retorno como por mágica dependendo dos parâmetros recebidos, como no exemplo abaixo, que o método pode retornar um List[Int] ou List[String] de acordo com os parâmetros passados.
samples/046_polimorfic_methods.scala

1
2
3
4
5
def dup[T](x: T, n: Int): List[T] =
  if (n == 0) Nil
  else x :: dup(x, n - 1)
println(dup[Int](3, 4))
println(dup("three", 3))

Neste exemplo é fácil de verificar o que esta acontecendo, pelo menos quando executamos o código, e a sintaxe para isto também é simples, o tipo variável é definido entre colchetes na declaração do método, como pode ser visto na linha 1, depois disto o tipo pode ser referenciado em qualquer parte do código dentro do mesmo método.
Outro recurso bastante interessante que também já vimos em outros exemplos, é a composição de classes, que funciona bem diferente das classes abertas do Ruby, mas é bem parecida com a composição de classes com modulos no Ruby, o recurso para composição de classes em Scala é o uso de “traits”, vamos dar uma nova olhada em um dos exemplos de traits.
samples/010_traits.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
trait Flyer {
  def fly = println("I can fly!")
}
trait Jumper {
  def jump = println("I can jump!")
}
trait Walker {
  def walk = println("I walk too!")
}
class Animal {
  def breathe = println("Dogs breath!")
}
class Dog extends Animal with Jumper {
}
class Bird extends Animal with Flyer with Jumper{
}
class Airplane extends Flyer {
}
object TraitsApp extends Application {
  val dog = new Dog with Walker
  val bird = new Bird
  val plane = new Airplane
  dog.breathe
  dog.jump
  dog.walk
  bird.breathe
  bird.jump
  bird.fly
  plane.fly
}

É possível definir parte do comportamento esperado em um grupo de classes utilizando traits, mas é possível adicionar traits também no momento em que uma classe é instanciada, como pode ser visto no exemplo abaixo:
samples/047_traits.scala

1
2
3
4
5
6
7
8
trait ToXML {
  def toXML = "<xml>" + toString() + "</xml>"
}
class TraitTest(name:String) {
  override def toString = "--" + name
}
var t = new TraitTest("Name Test") with ToXML
println(t.toXML)

E ainda temos as views, que são uma forma de criar conversões automáticas de tipos, em scala este recurso é chamado de “views”, a idéia é ter um método definido que sabe como converter um objeto em outro, e quando o compilador precisa explicitamente do segundo tipo, ou um método do segundo tipo é chamado no primeiro, o método de conversão é chamado automaticamente.
Isto parece confuso, então vamos ver um exemplo simples de como isto funciona para facilitar as explicações:
samples/048_views.scala

1
2
3
4
5
6
final class RichChar(c: Char) {
  def isDigit: Boolean = Character.isDigit(c)
    // isLetter, isWhitespace, etc.
}
implicit def charWrapper(c: Char) = new RichChar(c)
println('0'.isDigit)

Na linha 6, quando o método isDigit é chamado na literal de um caracter, o compilador verifica todas as possíveis conversões daquele objeto que possuam um método isDigit, verifica todos os métodos declarados como “implicit”, que recebam o objeto como parâmetro para fazer a conversão, e chama o método implicitamente para converter o objeto e chamar o método na classe que tem o método declarado.
Se existir algum conflito no escopo, por exemplo duas possíveis conversões, um erro sera gerado, neste caso a conversão precisara ser feita manualmente.

E acredito agora que estamos prontos para voltar a falar de programação funcional em scala, já conhecemos o suficiente para escrever código Java em scala, mas a idéia de se aprender uma nova linguagem é aprender nvoas formas de se resolver problemas, então nos próximos posts vamos entrar um pouco mais no mundo funcional da linguagem, vamos entender as implementações e pelo menos conseguiremos lêr muitos dos códigos complicados em scala que vemos por ai …

E por último, se você leu até aqui, talvez você queira me seguir no twitter ou assinar o feed do blog.

Você gostou deste post? Compartilhe:

Tags: , , ,

03 May 11 Scala: Atores – concorrência implementada de forma fácil e segura

Bom, este post atrazou bastante, mas vamos ver se volto a periodicidade padrão. Só para não perder o histórico, este é o oitavo post da série sobre Scala, já falamos de o básico da linguagem scala, logo depois Orientação a objetos em scala, um pouco sobre closures, depois compreensão de listas e mapas, depois Programação Funcional em Scala e na semana passada sobre tipos parametrizados e por último falamos de pattern matching e case classes. Mas deixando de lenga lenga, hoje vamos falar de Actors, que são a solução Scala para programação concorrente.


Atualmente um dos maiores desafios de todas as linguagens e plataformas de desenvolvimento é aproveitar melhor os multiplos nucleos dos computadores modernos, que mesmo quando não são multi processados, o processador possui mais de um nucleo, permitindo a execução realmente em paralelo de partes do código, coisa que a maior parte das linguagens já tinha suporte, mas que a grande maior parte dos programadores não estava preparada para utilizar.
A resposta da Scala para facilitar a programação concorrente são os Actors (ou atores), que são analogos aos Threads do Java, mas tem muitos recursos para facilitar a vida do programador e uma grande diferença, Actors em scala possuem um protocolo definido para se comunicarem, eles enviam mensagens uns para os outros em vez de acessar as mesmas variáveis que outras threads.
As mensagens são processadas utilizando match case, então normalmente as mensagens são case objects que estudamos no artigo anterior, isto permite que dados não alteráveis sejam passados de uma ator para outro evitando assim a grande maior parte dos problemas existentes na programação concorrente.

Vamos ver um exemplo simples para começar a entender como criar Actors e depois tentarei mostrar uma situação para tirar proveito deles.

samples/040_actor.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import scala.actors.Actor
import scala.actors.Actor._
 
case object Ping
case object Pong
case object Stop
 
class Ping(count: Int, pong: Actor) extends Actor {
  def act() {
    var pingsLeft = count - 1
    pong ! Ping
    while (true) {
      receive {
        case Pong =>
          if (pingsLeft % 1000 == 0)
            Console.println("Ping: pong")
          if (pingsLeft > 0) {
            pong ! Ping
            pingsLeft -= 1
          } else {
            Console.println("Ping: stop")
            pong ! Stop
            exit()
          }
      }
    }
  }
}
 
class Pong extends Actor {
  def act() {
    var pongCount = 0
    while (true) {
      receive {
        case Ping =>
          if (pongCount % 1000 == 0)
            Console.println("Pong: ping "+pongCount)
          sender ! Pong
          pongCount = pongCount + 1
        case Stop =>
          Console.println("Pong: stop")
          exit()
      }
    }
  }
}
 
val pong = new Pong
val ping = new Ping(100000, pong)
ping.start
pong.start
Thread.sleep(5000)

Agora um pouco de explicação sobre o código:

  • Para criar um Actor extendemos a classe Actor, como podemos ver nas linhas 8 e 30
  • Para enviar uma mensagem para um ator, utilizamos um opeador novo, o “!”", como podemos ver nas linhas 18, 22 e 38
  • Quando um Actor recebe uma mensagem, ele pode responder a mensagem, ou seja, enviar uma mensagem para quem enviou a mensagem que esta sendo processada utilizando o método “sender” como podemos ver na linha 38
  • Semelhante aos Threads do Java, um Actor precisa ser iniciado, e para isto usa-se o método “start” como podemos ver nas linhas 50 e 51
  • Após extender a classe Actor, é necessário implementar o método abstrato “act”
  • Para receber as mensagens é necessário chamar o método “receive” e passar um bloco para este, onde a mensagem sera processada
  • O método “receive” bloqueia a thread até que uma mensagem seja recebida
  • Os atores trabalham inicialmente em um pool de threads com tamanho inicial 4, este pool aumenta automaticamente quando há mais trabalho para ser realizado
  • é possível implementar os actors sem a utilização de threads, eles podem funcionar baseados em mensagens, substituindo o método “receive” pelo método “react” mas este não pode ser chamado dentro de um while, para resolver isto, foi criado o método “loop”, o exemplo alterado pode ser visto abaixo

samples/041_actor.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import scala.actors.Actor
import scala.actors.Actor._
 
case object Ping
case object Pong
case object Stop
 
class Ping(count: Int, pong: Actor) extends Actor {
  def act() {
    var pingsLeft = count - 1
    pong ! Ping
    loop {
      react {
        case Pong =>
          if (pingsLeft % 1000 == 0)
            Console.println("Ping: pong")
          if (pingsLeft > 0) {
            pong ! Ping
            pingsLeft -= 1
          } else {
            Console.println("Ping: stop")
            pong ! Stop
            exit()
          }
      }
    }
  }
}
 
class Pong extends Actor {
  def act() {
    var pongCount = 0
    loop {
      react {
        case Ping =>
          if (pongCount % 1000 == 0)
            Console.println("Pong: ping "+pongCount)
          sender ! Pong
          pongCount = pongCount + 1
        case Stop =>
          Console.println("Pong: stop")
          exit()
      }
    }
  }
}
 
val pong = new Pong
val ping = new Ping(100000, pong)
ping.start
pong.start
Thread.sleep(5000)

Uma coisa interessante sobre o “react” é que ele pode ser encadeado, ou seja é possível escrever lógicas mais complexas com diversas mensagens para completar a atuação de um ator.

Com isto podemos tentar montar um exemplo um pouco mais interessante, simulando uma peça de teatro, bom, na verdade não vai ser nada tão complexo, vão ser apenas 3 atores brincando de telefone sem fio para que possamos ver mais um recurso interessante dos atores, veja o exemplo asseguir e logo depois vou explicar os pontos importantes do código:

samples/042_actor.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import scala.actors.Actor
import scala.actors.Actor._
 
class Messenger(mediator : Actor) extends Actor {
  def act() {
    mediator ! Some("Hello")
    loop {
      react {
        case Some(x) => {
          Console.println(x)
          mediator ! None
        }
        case None => exit()
      }
    }
  }
}
 
class Mediator(destiny : Actor) extends Actor {
  def act() {
    loop{
      react {
        case Some(x) => {
          destiny ! Some(x)
          reply {
            receive {
              case x: Option[_] => x
            }
          }
        }
        case None => {
          destiny ! None
          sender ! None
          exit()
        }
      }
    }
  }
}
 
class Destiny extends Actor {
  def act() {
    while(true) {	
      receive {
        case Some(x) => { 
          println(x)
          Thread.sleep(100)
          sender ! Some("World")
        }
        case None => exit()
      }
    }
  }
}
val destiny = new Destiny
val mediator = new Mediator(destiny)
val messenger = new Messenger(mediator)
destiny.start
mediator.start
messenger.start
Thread.sleep(10000)

Os pontos importantes neste exemplo são os seguintes:

  • Na linha 25 o método “reply” é utilizado para retornar uma mensagem para o Actor que enviou a mensagem que esta sendo processada. O mesmo comando pode ser utilizado para retornar qualquer valor para quen enviou a mensagem, mesmo não sendo um Actor
  • na linha 26, é possível utilizar um “receive” dentro de um “reply”, nesta parte do código o “react” não vai funcionar
  • O bloco entre as linhas 26 e 28, retorna como resultado a mesma coisa que recebeu como mensagem, neste caso um Option
  • O Thread.sleep só esta na linha 61 por que não sabemos quanto tempo o código precisa para enviar e receber as mensagens, e como os Actors não rodam na thread principal, a aplicação seria finalizada antes que a troca de mensagens ocorresse, no próximo exemplo veremos como evitar isto

E por último, vamos dar uma olhada em outras formas de mandar mensagens para Actors e como iteragir com eles de fora do código de um Actor.
samples/043_actors.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import scala.actors.Actor
import scala.actors.Actor._
 
class PingPongPlayer() extends Actor {
  def act() {
    loop{
      react {
        case x:String => {
          println("Ping")
          reply("Pong")
        }
        case _ => exit()
      }
    }
  }
}
println("Start")
val ppp = new PingPongPlayer
ppp.start
val resp = (ppp !! "Ping")
println(resp())
val resp1 = (ppp !? "Ping")
println(resp1)
val resp2 = (ppp !? (100,"Ping"))
println(resp2)
ppp ! None

Os operadores utilizados para enviar as mensagens desta vez foram “!!” e “!?”.

  • o primeiro retorna um objeto do tipo Future, que vai armazenar o retorno da mensagem enviada, quando se executa o Future, se ele ainda não tem o retorno, ele vai ficar esperando e bloquear a thread atual até que algum resultado seja retornado.
  • o segundo, quando executado sem um timeout tem o mesmo comportamento, com a diferença que ele bloqueia a thread até receber o retorno já no envio da mensagem. Como foi chamado na linha 22
  • o mesmo operator pode ser invocado passando-se um timeout, este timeout define quanto tempo a thread pode ser bloqueada na espera de uma resposta, como pode ser visto na linha 24

Bom, acho que por hoje era isto, não consegui pensar em uma aplicação simples para montar o exemplo, mas acho que apenas estes exemplos já ajudam bastante.
Se você leu até aqui, gostaria de deixar umas pergunta para que sejam respondidas nos comentários:
1) Baseado em sua experiência, quais vantagens você acha que os atores trazem sobre a implementação de thread padrão, como por exemplo de Java ou C#?
2) Você costuma escrever códigos pensando em execução concorrente? Quais técnicas você usa para garantir que seu código é thread safe?

E por último, se você leu até aqui, talvez você queira me seguir no twitter ou assinar o feed do blog.

Você gostou deste post? Compartilhe:

Tags: , , ,

28 Mar 11 Scala: match e case classes

Este é o sétimo post da série sobre Scala, o primeiro foi o básico da linguagem scala, logo depois Orientação a objetos em scala, um pouco sobre closures, depois compreensão de listas e mapas, depois Programação Funcional em Scala e na semana passada sobre tipos parametrizados. Agora vamos falar um pouco de operadores condicionais, pattern matching, type matching e outras possíveis implementações para programação condicional.

Pattern Matching

Como eu disse que começaria neste artigo com exemplos mais complexos do que os pedaços de código dos artigos anteriores, a minha proposta para o primeiro exemplo é implementar uma calculadora, provavelmente de mais de uma forma para mostrar várias formas de fazer as mesmas operações, lembrem-se que este vai ser um exemplo simples.

Vamos começar vendo como funciona o pattern matching, este seria o equivalente a um case na linguagem scala, mas um pouco mais flexível do que um case do java, vamos ver no método calculadora abaixo.

samples/034_calculator.scala

1
2
3
4
5
6
7
8
def calculate(value1 : Int, value2 : Int, operation : String) : Int = operation match {
	case "+" => value1 + value2
	case "-" => value1 - value2
	case "*" => value1 * value2
	case "/" => value1 / value2
	case _ => -1
}
println(calculate(20, 25, "+"))

Como eu comentei, o match pode ser utilizado exatamente como o switch/case do java ou do C++, mas ele é um pouco mais flexível do que isto, vamos implementar a mesma calculadora agora, utilizando type matching, e utilizando classes para implementar cada uma das operações da calculadora. Mas antes perceba o “_” na última posição do case, que vai funcionar como uma condição default, o que não for capturado em nenhuma das outras condições cai ali.
Agora vamos a um exemplo com case classes, como são chamadas em Scala.

samples/035_calculator.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Operation
case class Addition(value1: Int, value2:Int) extends Operation 
case class Subtraction(value1: Int, value2:Int) extends Operation 
case class Multiplication(value1: Int, value2:Int) extends Operation 
case class Division(value1: Int, value2:Int) extends Operation 
 
def calculate(op : Operation) : Int = op match {
	case Addition(x,y) => x + y
	case Subtraction(a,b) => a - b
	case Multiplication(c,d) => c * d
	case Division(e,f) => e / f
	case _ => -1
}
println(calculate(Addition(30,45)))

Os case classes são úteis apenas no case de pattern matching, as variáveis de instância podem ser utilizadas diratamente no corpo do case como pode ser visto. Esta é uma boa opção para simular uma enumeração, já que as case classes de uma hierarquia tem que ser obrigatoriamente definidas no mesmo arquivo fonte. Esta é uma estrutura bastante utilizada em programação funcional, mas para quem esta acostumado com programação orientada a objetos esta parece uma abordagem um pouco pobre, por não possuir métodos definidos dentro das classes.
Como scala é uma linguagem mista, ou seja alem de funcional é também orientada a objetos, podemos utilizar o pattern matching para fazer type matching, como podemos ver no terceiro exemplo de calculadora abaixo.

samples/036_calculator.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abstract class Operation
class Addition(value1: Int, value2:Int) extends Operation {
	def add() = value1 + value2
}
class Subtraction(value1: Int, value2:Int) extends Operation  {
	def sub() = value1 - value2
}
class Multiplication(value1: Int, value2:Int) extends Operation  {
	def mult() = value1 * value2
}
class Division(value1: Int, value2:Int) extends Operation  {
	def div() = value1 / value2
}
 
def calculate(op : Operation) : Int = op match {
	case a:Addition => a.add()
	case s:Subtraction => s.sub()
	case m:Multiplication => m.mult()
	case d:Division => d.div()
	case _ => -1
}
println(calculate(new Addition(30,45)))

Como podemos ver neste exemplo, fazer type matching é bastante simples, claro que poderiamos ter declarado métodos nas case classes e chamado estes métodos, mas esta não é a ideia das mesmas.

As case classes tem a fantagem de poder ser instanciadas sem a utilização do “new” mas tem limitações de todas as classes de uma hierarquia precisarem ser definidas no mesmo arquivo, a idéia delas é serem utilizada como um tipo de enumeração.

Alem do pattern matching, scala ainda tem suporte aos operadores condicionais padrão da maioria das linguagens imperativas, como o if, else, operadores de loop como o do, while, mas o código funcional sempre dapreferencia para a utilização do pattern matching, como no exemplo abaixo.

samples/037_calculator.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
abstract class Operation {
	def calculate() : Int;
}
class Addition(value1: Int, value2:Int) extends Operation {
	def calculate() = value1 + value2
}
class Subtraction(value1: Int, value2:Int) extends Operation  {
	def calculate() = value1 - value2
}
class Multiplication(value1: Int, value2:Int) extends Operation  {
	def calculate() = value1 * value2
}
class Division(value1: Int, value2:Int) extends Operation  {
	def calculate() = value1 / value2
}
 
def calculate(op : Operation) : Int = op match {
	case null => throw new Exception("The parameter should not be null")
	case _ => op.calculate()
}
println(calculate(new Addition(30,45)))

Neste exemplo o pattern matching esta sendo utilizado para verificar se o objeto é nulo ou não, a diferença em linhas de código é insignificante, mas a forma de pensar é em diferente de uma linguagem imperativa, no caso do if por exemplo. Em linguagens funcionais, assim como em algumas de paradigma misto como o Scala e o Python, é bastante comum a utilização de mapas para tomar decisões sobre qual caminho seguir, ou qual código executar, e é exatamente isto que o pattern matching é, uma forma especializada de mapa utilizado para tomar decisões, inclusive em diversos exemplos encontrados na internet, são misturados tipos de matching diferentes no mesmo bloco mostrando a flexibilidade da ferramenta.

Uma calculadora gráfica

Bom, a calculadora criada até agora não me parece uma aplicação mais complexa do que os métodos soltos que estavamos criando até agora, então vamos fazer exatamente a mesma calculadora, mas com um pouco de código swing, utilizando o pacote java.swing, hoje não vou detalhar muito os recursos, apenas comentar o código do exemplo, mas se vocês se interessarem, deixem comentários que escrevo um artigo só sobre o wrapper scala.swing.

Então vamos ao código, a minha idéia é ter uma calculadora com dois campos de entrada de números, um botão para cada uma das 4 operações básicas, e um label para o resultado.
O layout vai ser simples, os dois campos de entrada numérica estarão bem no topo da tela, logo abaixo o label para o resultado, e abaixo disto os botões, o usuário deve preencher os dois campos numéricos, e quando um botão de uma das operações for precionado, o resultado da operação devera aparecer no label.

Vamos ver o código necessário para isto, primeiro apenas para o layout, depois eu adiciono a lógica na tela para fazer ela funcionar.

samples/038_calculator.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import swing._
object Calculator extends SimpleSwingApplication {
	def newTextField =  new TextField {
		text = "0"
		columns = 5
	}
	var input1 = newTextField
	var input2 = newTextField
	var resultLabel = new Label {
		text = "0"
	}
	var buttons = new GridPanel(2,2) {
		List("Add", "Sub", "Mult", "Div") foreach { contents += new Button(_) }
	}
	def top = new MainFrame {
		title = "Calculator"
		contents = new GridPanel(4,1){
			List(input1, input2, resultLabel, buttons) foreach {contents += _ }
		}
	}
}

Este é um exemplo simples dos wrappers scala para Swing, a aplicação básica é criada utilizando-se o objeto SimpleSwingApplication.
A classe SimpleSwingApplication faz o bootstrap do swing, e facilita o desenvolvimento, ela precisa implementar um método de nome “top” que retorne um MainFrame, este main frame vai ser a tela principal da aplicação.

As classes de objetos principais do SWING tem wrappers com contrução facilitada e propriedades mais amigáveis para a utilização com o Scala.

Agora vamos dar uma olhada em como fazer os objetos, principalmente os botões tratarem os eventos, vamos ver o exemplo de código e depois eu explico ele.

samples/039_calculator.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import swing._
import swing.event._
abstract class Operation {
	def calculate() : Int;
}
class Addition(value1: Int, value2:Int) extends Operation {
	def calculate() = value1 + value2
}
class Subtraction(value1: Int, value2:Int) extends Operation  {
	def calculate() = value1 - value2
}
class Multiplication(value1: Int, value2:Int) extends Operation  {
	def calculate() = value1 * value2
}
class Division(value1: Int, value2:Int) extends Operation  {
	def calculate() = value1 / value2
}
object Calculator extends SimpleSwingApplication {
	def newTextField =  new TextField {
		text = "0"
		columns = 5
	}
	var input1 = newTextField
	var input2 = newTextField
	var resultLabel = new Label {
		text = "0"
	}
	def calculate(op : Operation) : Unit = op match {
		case null => throw new Exception("The parameter should not be null")
		case _ => resultLabel.text = "" + op.calculate()
	}
 
	var buttons = new GridPanel(2,2) {
		List(Action("Add"){
			calculate(new Addition(input1.text.toInt,input2.text.toInt))
			}, 
			Action("Sub"){
			calculate(new Subtraction(input1.text.toInt,input2.text.toInt))
			}, 
			Action("Mult"){
			calculate(new Multiplication(input1.text.toInt,input2.text.toInt))
			}, 
			Action("Div"){
			calculate(new Division(input1.text.toInt,input2.text.toInt))
			}) foreach { contents += new Button(_)}
	}
	listenTo(input1.keys,input2.keys)
	reactions += {
		case KeyPressed (source, key, modifiers, location) => println("Clicked " + key)
	}
	def top = new MainFrame {
		title = "Calculator"
		contents = new GridPanel(4,1){
			List(input1, input2, resultLabel, buttons) foreach {contents += _ }
		}
	}
}

Neste exemplo temos duas formas de “ouvir” eventos do SWING em scala, uma delas é no caso dos botões, que só interessa a ação do botão, disparada em um clique ou com o enter, utilizei a classe Action para criar o botão, e a segunda forma mais flexível é a combinação do método “listenTo” com a propriedade “reactions”.
No “listenTo” informamos que queremos ouvir os eventos dos objetos listados, e em reactions adicionamos blocos de código que serão utilizados para processar os eventos, neste caso utilizamos a case class disponível para o evento “KeyPressed” para logar quais teclas foram precionadas nos campos de entrada de texto.

Claro que este exemplo de calculadora não é nenhum exemplo de código perfeito, mas eu queria apenas misturar um pouco de swing com um dos exemplos de código deste artigo, acho que não foi uma boa forma de mostrar uma aplicação mais complexa, mas a partir do próximo post vamos falar de actors em scala, e como eles podem te ajudar, isto e um pouco de programação web em um framework escrito em scala.

Se vocês quiserem um artigo mais detalhado sobre os wrappers de scala para programação com SWING é só falar nos comentários que eu escrevo um ASAP.

Por enquanto, espero que a utilização do match e das case classes tenha ficado clara. Se não ficou, se tiverem dúvidas, sugestões ou criticas, por favor deixe seu comentário.

Você gostou deste post? Compartilhe:

Tags: , , , ,

14 Mar 11 Scala: Tipos parametrizados, limites superiores, inferiores e métodos polimórficos

Este é o sexto post da série sobre Scala, o primeiro foi o básico da linguagem scala, logo depois Orientação a objetos em scala, um pouco sobre closures, depois compreensão de listas e mapas, e na semana passada sobre Programação Funcional em Scala. Outra coisa bastante comum em todo código Scala que vejo por ai são Type Parameters, que em Scala podem assumir mais de uma forma, e também precisamos entender como funcionam upper e lower bounds, ou limites superiores e inferiores.

Vamos começar com alguns exemplos, e vamos analisar os códigos para tentar deixar claro como isto funciona e como podemos utilizar este recurso bastante poderoso a nosso favor.

(este código não é executável, é apenas um exemplo)
samples/022_typeparamsbasic.scala

1
2
3
4
5
6
7
8
9
//Declaração parcial da classe Array em Scala
class Array[A] {
def apply(index: Int): A
def update(index: Int, elem: A)
}
 
val x = new Array[String]()
val y: Array[Any] = x
y(0) = new Rational(1, 2) // Açucar sintatico para y.update(0, new Rational(1, 2))

Neste exemplo, podemos ver um exemplo simples da declaração de uma classe parametrizável, e também podemos ver um problema gráve gerado por esta parametrização, o código das linhas 7 e 8, é válido, já que String é subclasse de Any, mas como não existe nenhuma verificação em tempo de execução, teriamos problemas na linha 9, onde um objeto diferente de string seria colocado no Array, o java resolve este problema com uma verificação em tempo de execução, o que fere um pouco a idéia de tipagem estática da linguagem, em scala é possível mudar a declaração da classe para melhorar um pouco esta verificação, adicionando um pouco de variância na declaração da classe e do método …

(este código não é executável, é apenas um exemplo)
samples/032_typeparams2.scala

1
2
3
4
5
//Declaração parcial da classe Array em Scala
class Array[A+] {
def apply(index: Int): A
def [B <: A]update(index: Int, elem: B)
}

A notação A+ diz que o tipo A só deve ser utilizado em posições covariantes, e na nova assinatura do método update, que agora é um método polimorfico, não é mais utilizado o tipo A, agora utilizamos o tipo B, que é definido na própria assinatura do método, como sendo um sub tipo de A, assim resolvendo o problema de inserir objetos que não são strings, o método só vai aceitar strings ou seus sub tipos.

Scala possui as seguintes notações para variância e limites superiores e inferiores de tipos:

Notação Explicação
A+ A só pode ser utilizado em posições de covariância
A- A só pode ser utilizado em posiçÕes de contravariância
B <: A B é menor ou igual a A, ou seja, B é A ou um de seus subtipos
B >: A B é maior ou igual a A, ou seja, B é A ou um super tipo deste
B <: A >: C B é um subtipo de A que é um super tipo de C

Isto torna as coisas bastante flexíveis para definição de tipos e de parâmetros para métodos, permitindo que recebamos apenas o que sabemos como utilizar.
Mas tenham cuidado com os métodos polimorficos, eles são poderosos mas podem deixar o código um tanto quanto difícil de ser lido.

Vamso dar uma olhada em um exemplo um pouco mais complexo agora, retirado da documentação da linguagem e explicar os detalhes dele.

samples/033_typeparams3.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Stack[+A] {
  def push[B >: A](elem: B): Stack[B] = new Stack[B] {
    override def top: B = elem
    override def pop: Stack[B] = Stack.this
    override def toString() = elem.toString() + " " +
                              Stack.this.toString()
  }
  def top: A = error("no element on stack")
  def pop: Stack[A] = error("no element on stack")
  override def toString() = ""
}
 
var s = new Stack[Any]().push("hello");
s = s.push(new Object())
s = s.push(7)
Console.println(s)

Na linha 2, definimos que o parâmetro do método push vai ser qualquer super tipo do tipo inicial, basicamente isto faz o método aceitar qualquer coisa, por que a declaração vai até Any, neste método é criada uma nova classe, descendente de Stack, que sobre escreve os métodos “top”, “pop” e “toString”.
Na linha 13 criamos uma nova instância de Stack[Any], em que o método push vai ser declarado como push(Any), já que não existe um super tipo de Any, e push(Any) vai aceitar qualquer valor como parâmetro.

Bom, não sei se neste artigo ajudei ou atrapalhei, mas se tiverem dúvidas, criticas ou sugestões, como sempre é só deixar um comentário que vou tentar responder o mais rápido possível.

Você gostou deste post? Compartilhe:

Tags: , , , ,

11 Mar 11 Comparação entre TDD e BDD – Como aprender um me ajudou com o outro

Comparação dos ciclos de desenvolvimento de TDD e BDD


Ja falei um pouco sobre isto quando traduzi o post de introdução ao BDD do Don North, achei o texto excelente e a explicação melhor ainda, mas o que poderiamos esperar de quem inventou o termo Behavior driven Development, certo?
Mas aquele texto é bastante longo e no post da semana passada me pediram para falar um pouco sobre BDD diretamente, sem uso de ferramentas e como ele se relaciona ao TDD, como um ajuda o outro, e este post é a minha tentativa de responder a estas perguntas.

A imagem no início deste post é uma comparação entre os ciclios básicos de desenvolvimento de TDD e BDD, se você prestar um pouco de atenção, vai ver que os dois são bem semelhantes, utilizei a mesma imagem base do ciclo do BDD que eu fiz para o post da semana passada, e fiz uma nova para o TDD, se ignorarmos as fazes de executar os testes, que na imagem do BDD estão implicitas, elas existem no ciclo, só não estão ali para aumentar o foco nas outras etapas, os dois processos são praticamente os mesmos, vamos colocar em uma tabela para facilitar a comparação.

Fase TDD BDD
1 Escrever um teste Escrever uma especificação do comportamento esperado
2 Executar os testes e ver eles falhando Executar as especificações e ver elas falhando
3 Escrever código suficiente para que o teste passe Escrever código suficiente para implementar o comportamento esperado/fazer a especificação passar
4 Executar os testes e ver eles passando Executar as especificações e ver elas passando
5 Refatorar o código Refatorar o código

As fases são exatamente as mesmas, a diferença é que em TDD se fala em testes, enquanto em BDD se fala em comportamento, então vamos ver algumas semelhanças e fazer algumas traduções para ver que as coisas são mais iguais do que parecem.

Uma especificação do comportamento esperado, em BDD, pode ser simplesmente um teste JUnit/RUnit/NUnit, validando se um método de um objeto se comporta da forma esperada.
Um teste no TDD, pode ser um teste JUnit/RUnit/NUnit, verificando se um método de um objeto, dado um conunto de parâmetros gera um output esperado, ou seja, se ele se comporta de forma esperada.

Escrever um teste ou especificação quer dizer praticamente a mesma coisa, são duas formas de dizer que vamos escrever um teste automatizado para validar o que um método deve fazer.

Ah, então as doias coisas são iguais? TDD e BDD são a mesma coisa?
Não, são bem diferentes até, são abordagens diferentes para solucionar o mesmo problema, o BDD evoluiu do TDD, ele não tenta criar nada novo, tenta facilitar as coisas para a maior parte dos desenvolvedores.
Um ótimo desenvolvedor, trabalhando com TDD, na prática esta testando o comportamento dos métodos, das classes e do sistema como um tudo, um desenvolvedor ruim, trabalhando com TDD, esta na prática testando como os métodos foram implementados.

Já com BDD as coisas são diferentes, tanto o bom desenvolvedor quando o ruim, vão escrever testes para validar o comportamento dos métodos, classes e do sistema como um todo, por uma simples mudança de abordagem e nomenclatura das coisas. Pelo menos foi o que aconteceu comigo.

Por muito tempo, sempre que eu trabalhava com TDD, eu cometia diversos erros, e tinha muitas dúvidas, como por exemplo:

  1. Como chamar as minhas classes e meus métodos do sistema?
  2. O que eu devo testar neste método?
  3. Qual deve ser o nome do método de testes?
  4. Até onde eu devo testar?
  5. Se o cliente me perguntar o que eu estou fazendo agora, o que eu digo?

Vou falar um pouquinho sobre cada uma destas dúvidas, e como o BDD me ajudou.

Como chamar as minhas classes e meus métodos do sistema?

Qualquer desenvolvedor iniciante tem dúvidas parecidas com esta, em algum momento da vida começamos a utilizar nomenclaturas vindas de Design Patterns, depois alguns tem sorte de ouvir falar de DDD (Domain Driven Design), e os mais inteligentes começam a utilizar DDD e começam a colocar nas classes e métodos nomes relacionados com os problemas de negócio que estes estão ajudando a resolver, ou as entidades do mundo real que as classes e métodos representam,

Isto não tem diretamente a ver com TDD, mas eu já havia ouvido falar de DDD, até achei que utilizava, mas só comecei a entender realmente DDD e a usar quando utilizei BDD em um ou dois projetos, por que o BDD reforça que temos que utilizar uma linguagem única na analise, design e implementação do sistema.

Claro que é possível contornar isto, mas também é possível utilizar o MS Word para escrever código, mas não é por isto que você vai querer fazer isto.

O que eu devo testar neste método? e Qual deve ser o nome do método de testes?

Eu tinha um problema que descobri depois ser bastante comum, vamos pensar na seguinte situação hipotética:
Tenho uma classe de nome Cliente e um método de nome criar, preciso escrever testes para este método.
Seguindo TDD como um principiante, vou olhar alguns tutoriais, e vou fazer mais ou menos isto:
Criar uma classe chamada ClienteTest, nesta classe vou criar um método testCriar, e depois disto dentro deste método vou popular um cliente, e chamar o método criar, depois vou usar o método list ou get para verificar se o cliente foi criado.
Até ai sem problemas, mas eu testei se o método cria dois clientes com o mesmo nome ou CPF? testei outra validações?
Vou colocar todos estes testes dentro do mesmo método testCriar? Se a resposta for não, como vou chamar o outro método que esta testando também o método criar?

Com BDD a abordagem de um iniciante que leu um tutorial na internet seria diferente, ele iria criar uma classe ClienteSpec, e criar o método shouldCreate e neste método iria popular um cliente e chamar o método create, e depois usar o método list ou get pata verificar se o cliente foi criado.
Mas qual a diferença?
No momento de verificar se o método cria dois clientes com o mesmo nome ou CPF, seguindo o padrão BDD que este iniciante aprendeu em um tutorial, ele vai criar um método shouldNotCreateWithDuplicatedName e outro shouldNotCreateWithDuplicatedCPF, já sabendo o quanto deve testar em cada um dos métodos, e qual deve ser o nome do método de testes, os nomes dos métodos de testes em BDD devem seguir o padrão:
should

Apenas a idéia de trocar o verbo test por should já ajuda quem esta começando a pensar melhor em qual deve ser o conteúdo daquele teste, e principalmente, ajuda a entender que se o comportamento mudou o teste não é mais válido, já que esta testando um comportamento inválido, diminuindo o trauma de apagar testes inválidos, que muita gente tem por ai.

Até onde eu devo testar?

Até onde devo testar é mais dificil de responder, o ideal seria testar todas as possibilidades de erro, mas isto raramente é possível, e pensar em comportamento em vez de testes me ajuda a pensar sobre o que vale mais a pena testar, e o que é mais importante ser testado.

Se o cliente me perguntar o que eu estou fazendo agora, o que eu digo?

Ai alguem vai dizer que o cliente é quem deve priorizar o que deve ser feito ou não, e eu vou dizer que com BDD, utilizando uma linguagem única em todas as fases do desenvolvimento, fica mais fácil até mesmo de explicar o que esta sendo feito naquele momento.
Nenhum cliente vai querer saber quais métodos do sistema estão sendo testados, eles apenas assumem que nós desenvolvedores testamos todo e qualquer poblema possível, mas algumas vezes precisamos da ajuda do cliente para entender o que ele quer, e algumas vezes eles querem saber como o dinheiro deles esta sendo gasto, e isto é mais comum do que muitos desenvolvedores iniciantes pensam.
É ilusão achar que com BDD um cliente vai escrever as especificações em qualquer que seja a linguagem para nós, mas utilizando no código a mesma linguagem do dominio do cliente, e testando o comportamento, podemos dizer para ele que estamos testando se o sistema não vai cadastrar usuários com nomes duplicados, que ele vai entender muito mais fácil do que se dissermos que estamos testando o método User.create.
(Lembre-se de que este foi um exemplo simplório e de baixissimo nível, uma conversa com este conteúdo raramente vai acontecer em um ambiente saudável)

Como aprender BDD me ajudou a trabalhar melhor com TDD e por que eu não uso BDD sempre

Em diversas situações não é possível utilizar BDD, muitas vezes não é possível utilizar DDD, que é um requisito para BDD, muitas vezes não existem especificações claras antes de se trabalhar no código, e na maior parte das vezes o projeto já estava utilizando TDD e não tem por que isto ser alterado.
Mas depois que aprendi os conceitos de BDD, não tenho mais as mesmas dúvidas, eu sei até onde testar, por que o que deve ser testado é o comportamento do método, não como ele foi implementado, eu sei quanto testar por que cada método de teste testa um comportamento diferente, eu sei como conversar com o cliente por que eu aprendi a mapear responsábilidades do sistema para a linguagem do dominio do cliente, mesmo quando o código não esta escrito desta forma.

Ou seja, a forma de pensar do BDD me ajudou a ser um desenvolvedor melhor.

Por que então existem ferramentas especificas para BDD, se é possível utilizar as mesmas conhecidas de TDD

Bom, esta pergunta é um pouco mais fácil de responder.
Há alguns anos atraz, eu fui em Punta Del Este a trabalho, e no caminho de volta passei em uma lancheria perto da rodoviaria, por que eu estava com fome.
Olhei o cardapio, e tentei pedir um sanduiche e uma coca cola com todo o meu portunhol, claro que a garçonete não entendeu lhufas do que eu disse, ai ela pediu para que eu falasse em portugues, que ela responderia em espanhol, e assim nos entendemos melhor e consegui o que eu queria, meu sanduiche e minha coca cola.
Conseguimos nos entender enquanto eu falava portugues, e ela falava espanhol, mas eu garanto que teria sido muito mais fácil se ambos falassemos a mesma lingua.
E este é o objetivo das ferramentas especificas para BDD, facilitar que exista uma linguagem única no dominio do problema do cliente, na analise do problema, na especificação de requisitos e na implementação do sistema.

Claro que as ferramentas não são perfeitas, e a maior parte deles se foca mais em testes de aceitação apenas, em vez de permitir que as especificações sejam escritas para toda e qualque parte do sistema, com a granularidade necessária, mas mesmo assim, o objetivo delas continua sendo permitir que seja utilizada uma linguagem única durante todo o ciclo de desenvolvimento como manda o BDD.

Considerações finais

Bom, acho que por hoje era isto, espero que tenha ficado claro por que eu gosto tanto de BDD, e por que estou escrevendo tanto sobre ferramentas feitas para BDD, não acho que BDD seja a bala de prata e solução para todos os problemas, mas da mesma forma que aprender uma nova linguagem de programação te faz um desenvolvedor melhor, aprender outros paradigmas e técnicas também faz.

Ainda mais no caso do BDD, que sendo uma “metodologia ágil quase completa”, inclui:

  • Testes automatizados
  • Domain Driven Design
  • Um pouco de analise de sistemas
  • Comunicação – que como eu já comentei antes já foi um problema grave meu

Aprender tudo isto, com certeza faz de qualquer um um desenvolvedor melhor.

Mas este é o meu entendimento sobre BDD e sobre TDD, pode ser que eu esteja errado, então como sempre, dúvidas, criticas e sugestões são bem vindos nos comentários do post.

Você gostou deste post? Compartilhe:

Tags: , , , , , ,

07 Mar 11 Programação Funcional em Scala

Este é o quinto post da série sobre Scala, o primeiro foi o básico da linguagem scala, logo depois Orientação a objetos em scala, um pouco sobre closures e na semana passada sobre compreensão de listas e mapas, agora vamso começar a entender por que eu gostei da linguagem scala, vamos entender um pouco mais sobre programação funcional, e como isto pode ajudar a escrever um código mais limpo e com uma menor probabilidade de bugs.

Diferente da maior parte dos artigos, vamos começar com um pouco de teoria desta vez:

Programação Funcional

Descrição tirada da Wikipedia.

Programação funcional é um paradigma de programação que trata a computação como uma avaliação de funções matemáticas e que evita estados ou dados mutáveis. Ela enfatiza a aplicação de funções, em contraste da programação imperativa, que enfatiza mudanças no estado do programa[1].
Uma função, neste sentido, pode ter ou não ter parâmetros e um simples valor de retorno. Os parâmetros são os valores de entrada da função, e o valor de retorno é o resultado da função. A definição de uma função descreve como a função será avaliada em termos de outras funções. Por exemplo, a função f(x) = x2 + 2 é definida em termos de funções de exponenciação e adição. Do mesmo modo, a linguagem deve oferecer funções básicas que não requerem definições adicionais.

OK, e por que isto vai me ajudar em alguma coisa, já que consigo fazer tudo o que quero em linguagens imperativas como o Java?

Simples, ou nem tanto, mas na programação funcional, o comportamento das funções não depende de nenhum estado externo as mesmas, todas as funções são auto contidas, e dados os mesmos parâmetros, retornarão sempre os mesmos resultados.
Na programação orientada a objetos, que estamos acostumados no Java, a saida de um método de um objeto qualquer, depende do estado daquele objeto, se alguma entidade externa, alterou o estado do objeto sem que percebessemos, o resultado da chamada do método pode não ser o que esperávamos.

Isto também quer dizer, que em uma linguagem puramente funcional, o que não é o caso de Scala, todas as funções são stateless, o que facilita a escrita de código auto contido, e facilita também a escrita de código distribuido, já que todo o código é stateless, posso chamar qualquer função passando os parâmetros corretos em qualquer ponto de um cluster ou em qualquer thread que o resultado vai ser o mesmo, sem os problemas de locking e compartilhamento de memória entre threads que conhecemos tão bem, ou pelo menos aqueles que já tentaram escrever aplicações multi threads conhecem.

Ficando rico com programação funcional

Para que você não diga que ninguem nunca ganhou nada por aprender outros paradigmas de programação, sendo mais especifico, que ninguem nunca ficou rico por aprender programação funcional, vamos conhecer agora dois conceitos bastante usados em programação funcional, e você já deve até ter ouvido falar deles, os conceitos são Map e Reduce, depois de aprender estes conceitos, e entender que eles poderiam ser utilizados em programação distribuida, o google foi criado :D

Mas chega de balela, vamos entender o que quer dizer Map Reduce.
Em programação funcional, map é simplesmente pegar uma lista, e aplicar em cada um dos itens a função Map, para criar uma nova lista, diferente da primeira, como no exemplo baixo:

samples/023_map.scala

1
2
3
4
5
6
7
8
import java.text._;
 
val square = (x : Int) => x * x
val toDate = (s : String) => new SimpleDateFormat("dd/MM/yyyy").parse(s)
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
val datestrings = List("18/02/1980", "29/05/2009", "30/07/1982", "10/09/1981")
println(numbers.map(square))
println(datestrings.map(toDate))

Como podemos ver no exemplo, a função map pega a primeira lista de valores e transforma em outra, algumas vezes de tipo diferente como foi visto no exemplo das datas, onde a função de Map, transformou as strings em objetos do tipo Date.

Já a função Reduce, pega uma lista e transforma em um item apenas aplicando uma função binária item por item, para quem programa em Ruby, é exatamente a idéia do inject da classe array, em scala no tipo List temos os métodos reduceLeft e reduceRight, que fazem a mesma coisa, mas começando da esquerda para a direita ou da direita para a esquerda respectivamente, como podemos ver no exemplo abaixo, como somar todos os itens de uma lista.

samples/024_reduce.scala

1
2
3
4
5
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
println(numbers.reduceLeft(_ + _))
 
val sum = (a : Int, b : Int) => a + b
println(numbers.reduceLeft(sum))

Agora, vamos imaginar o mesmo exemplo em java, uma linguagem imperativa, para ver se fica claro o que eu tentei dizer antes com a programação funcional ter menor tendencia a gerar erros (pelo fato de ser stateless):

samples/025_java_reduce.java

1
2
3
4
5
int sum = 0;
int numbers[] = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
for(int n : numbers){
  sum = sum + n;
}

Em java, não temos como aplicar uma função a uma lista, então a primeira solução que quase todos os programadores java vão pensar é fazer isto em um loop, para faze em um loop, é necessário armazenar o valor em uma variável externa ao loop para poder guardar o estado, ou pelo menos esta é a primeira solução implementada.
Ela funciona, agora se vocês perceberem, no exemplo em scala eu chamei duas vezes o reduce para calcular a soma de duas formas diferentes, imaginem o que aconteceria se eu executasse o loop para calcular a soma novamente em java, a probabilidade de eu esquecer de zerar a variável sum antes disto é bem maior que em scala, já que em scala não existe esta necessidade, desta forma, em um exemplo simples como este, já temos uma maior probabilidade de gerar um erro na versão imperativa do que na versão funcional do código.

Mas voltando ao tema de ficar rico, eu não sei como vocês podem ficar ricos, mas os fundadores do google utilizaram a idéia dos métodos Map e Reduce para criar o maior site de buscas existente hoje, acho que você é uma pessoa inteligente, que esta lendo o que eu escrevi, pode pensar em algo do gênero e ficar rico também, só não esqueça depois de quem deu a idéia :D

Funções, parâmetros e closures

Uma coisa importante na programação funcional é que praticamente não existe diferença entre funções e valores, por que uma função pode ser passada como parâmetro para outra função como qualquer outra variável, e da mesma forma que podemos utilizar o literal de um valor para passar o parâmetro para uma função, podemos passar o literal de uma função, ou seja, definir a função diretamente no parâmetro da segunda função.

Um exemplo disto pudemos ver no exemplo de reduce em scala, onde a função sum foi definida na chamada da função reduce na primeira chamada, mas uma coisa que não podemos esquecer, é que uma função não é a mesma coisa que uma closure, sendo que a última não é um recurso puramente funcional, mas ajuda bastante em linguagens multi paradigma como scala.
Closures guardam um ponteiro para o contexto onde foram definidas, sendo assim, elas já não são mais stateless, mas podem ser de grande ajuda em diversas situações. Apenas para mostrar a diferença, vou fazer um exemplo simples abaixo:

samples/026_closure.scala

1
2
3
4
5
6
7
var factor = 5
val multiplier = (x : Int ) => x * factor
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
 
println(numbers.map(multiplier))
factor = 10
println(numbers.map(multiplier))

Neste exemplo, diferente do exemplo de “map” anterior, não temos um comportamento puramente funcional, mas temos um comportamento que pode nos ajudar em diversas situações, o comportamento da função multiplier não é o mesmo em todas as situações dados os mesmos parâmetros, ele leva em consideração a variável local “factor” de onde ele foi declarado.

Mas em todas as situações, foram passadas funções como parâmetros para funções, dentro de outra função, não é possível identificar uma função ou uma closure, e uma pode ser parâmetro para a outra sem problema algum.

Operações básicas de programação funcional

As operações básicas em linguagens de programação funcional, quando estamos falando de trabalhar com estruturas de dados, são: Traversing, Filtering, Mapping, Reducing e Folding
Já vimos “map” e “reduce”, a operação “fold” é exatamente igual a reduce, com a diferença que ela recebe um parâmetro inicial, e começa a trabalhar sobre este valor, como no exemplo do reduce alterado para usar fold abaixo:

samples/027_fold.scala

1
2
3
4
5
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
val sum = (a : Int, b : Int) => a + b
println(numbers.foldLeft(2)(sum))
 
println(numbers.foldLeft(100)(sum))

E quanto a traversing, já fazemos isto desde o primeiro exemplo, a função padrão para list traversing em scala é o foreach, que podemos ver no exemplo abaixo:

samples/028_foreach.scala

1
2
3
4
5
6
7
8
List(1, 2, 3, 4, 5) foreach { i => println("Int: " + i) }
 
val stateCapitals = Map(
  "Alabama" -> "Montgomery",
  "Alaska"  -> "Juneau",
  "Wyoming" -> "Cheyenne")
 
stateCapitals foreach { kv => println(kv._1 + ": " + kv._2) }

Agora só faltou o filtering, que é feito com o método filter, que retorna uma coleção nova, apenas com os itens que retornaram true no método de filtragem, como no exemplo abaixo:

samples/029_filter.scala

1
2
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
println(numbers.filter{ x => x %2 == 0 })

Funções parcialmente aplicadas

Um recurso bastante interessante são as funções parcialmente aplicadas, a idéia é que podemos informar parte dos par&ametros de uma função agora, e o resto dos parâmetro depois, vamos ver o exemplo para deixar isto mais claro.

samples/030_partfunc.scala

1
2
3
4
5
6
7
def multiply(x : Int, y : Int) = x * y
val list = List(1, 2, 3, 4, 5, 6, 7)
 
println(list.map(multiply(5,_:Int)))
 
val multi10 = multiply(10,_:Int)
println(list.map(multi10))

Na linha 1, é criada uma função multiply, com 2 parâmetros, o retorno desta função é um parâmetro multiplicado pelo outro, até ai novidade nenhuma, mas lembra-se daquele método “map” das listas, que recebe como parâmetro um método com um só parâmetro? Quero utilizar o “multiply” que acabei de criar como parâmetro para este método, como podemos ver na linha 4, estou informando o primeiro parâmetro, e dizendo que o segundo parâmetro vai ser um integer, e que quem chamar esta função vai informar o valor para este par&ametro, se você executar o script, vera que todos os valores da lista serão multiplicados por 5.
Outra opção é armazenar a função parcialmente aplicada como uma variável, como podemos ver na linha 6, onde crio o método multi10, que na verdade é uma função parcialmente aplicada informando 10 como o valor do primeiro parâmetro.

Este recurso é bastante importante na programação funcional por que permite que utilizemos funções mais complexas de forma mais simples, e a cada vez que passamos uma função como parâmetro, diminuímos a complexidade necessária para chamar a mesma.

Currying

Scala também pode nos ajudar se já soubermos de antemão que vamos utilizar as funções parciaplemte aplicadas, com um recurso interessante chamado currying, a idéia é avisar o compilador que podemos passar parte dos parâmetros, e que isto deve gerar uma outra função, com os parâmetros que faltam, já na declaração, deem uma olhada no exemplo abaixo:

samples/031_currying.scala

1
2
3
4
5
def multiply2(x : Int)(y : Int) = x * y
 
def doIt(a : Int, f : (v : Int)=> Int) : Int = f(a)
 
println(doIt(3,multiply2(5)))

Na declaração da função multiply2, na linha 1, utilizamos parenteses para separar a lista dos parâmetros em vez de virgula como fizemos na maior parte das vezes até agora, quando isto é feito, se chamarmos a função apenas com um dos parâmetros, como fizemos na linha 5, o que o compilador faz é retornar uma função com os parâmetros que faltam, neste caso, exatamente o que a função doIt esperava no segundo parâmetro, uma função que precisa de um parâmetro Int e retorna um Int.

Tipos funções

Como sabemos Scala é uma linguagem fortemente tipada, e como podemos receber funções como parâmetro, é importante definir o que esperamos destas funções, qual a sua assinatura.

Como podemos ver no exemplo anterior, na linha 3 quando definimos a função doIt, definimos que o segundo parâmetro, precisava ser uma função, que receberia um valor do tipo Int e retornaria um Int, para fazer isto, a sintaxe é a seguinte:
(Tipo, Tipo, Tipo) => Tipo
Onde a lista de tipos dentro dos parenteses define os parâmetros, e o tipo depois do => define o tipo de retorno da função.

Considerações finais

Já aprendemos diversos recursos da linguagem Scala até agora, e neste artigo também um pouco de teoria sobre linguagens funcionais, ainda temos muito o que estudar sobre programação funcional em scala, mas ainda faltam algumas pontas a serem amarradas para entender todos os recursos.

No próximo artigo voltaremos a falar de alguns recursos da orientação a objetos da linguagem, mas acho que vou começar com exemplos um pouco mais complexos para que os textos fiquem mais interessantes.

Como sempre, dúvidas, sugestões e criticas podem utilizar os comentários, se não entenderam alguma coisa, por favor enviem suas perguntas.

Você gostou deste post? Compartilhe:

Tags: , , ,

04 Mar 11 Coletânea de posts sobre Cucumber e BDD, por que estou escrevendo e perguntas

Behavior Driven Development

Fluxo de trabalho Behavior Driven Development


Não é segredo para ninguem que eu gosto bastante do Cucumber e BDD, mas eu gostaria da opinião de vocês sobre o que mais vocês querem saber, então vamos começar pelo seguinte:
Até agora já publiquei 8 posts sobre Cucumber e BDD, segue a lista:

  1. Cucumber e BDD – Vantagens para a empresa (Argumentos para o gerente, para o arquiteto, para o presidente da empresa, …)
  2. Introduzindo BDD
  3. Ruby 101 – O básico de Ruby para poder utilizar tanto o Rails quanto o Cucumber
  4. Cucumber – Como testar aplicações Rails
  5. Cucumber – Como testar aplicações Web
  6. Cucumber – Como testar bibliotecas Java
  7. Cucumber: como testar bibliotecas ruby
  8. Testando aplicações web genéricas com Cucumber e Watir

Comecei a escrever esta coletânea de posts por que BDD realmente me ajudou a ser um desenvolvedor melhor. Eu sempre tive problemas com TDD por que eu achava os testes muito desconectados do motivo daquela classe ou método existir, depois que comecei a usar BDD eu simplesmente entendi o motivo de estar escrevendo aqueles testes, mesmo que eu estivesse escrevendo os mesmos testes que com TDD.
Na verdade eu não utilizo sempre o cucumber, não utilizo sempre o RSpec, as vezes uso Minitest ou JUnit mas a forma de pensar do BDD me ajudou a entender por que testar, o que testar e quanto testar.
E eu acho que não consegui passar esta mensagem nos posts até agora nestes posts sobre BDD e Cucumber, mas é esta a mensagem que eu quero passar, a idéia do BDD é te ajudar a responder pequenas perguntas importantes como estas:

  1. O que testar?
  2. Quanto testar?
  3. Como testar?

Então para ajudar a passar esta mensagem, o que vocês me sugerem? (respostas nos comentários por favor …)

  • Mostro a integração do cucumber com outras ferramentas?
  • Mostro outras ferramentas para facilitar a utilização de BDD em outras plataformas? Ferramentas comerciais?
  • Falo mais sobre BDD em posts, sem falar de ferramentas?
  • Falo sobre como utilizar os conceitos de BDD com outras ferramentas como RUnit, JUnit ou Minitest?
  • Falo sobre como aprender BDD pode ajudar a fazer TDD?
  • Falo como utilizar BDD para melhorar a comunicação entre as equipes de desenvolvimento, negócios, testes, … ?(exatamente, em alguns lugares existem equipes de desenvolvimento e testes, nem todos são Agile )
  • Você tem outras idéias para me ajudar a passar estas mensagens simples? pode deixar um comentário com a sua sugestão …

Muito obrigado pela opinião de vocês, aguardo comentários, por favor peçam para que desenvolvedores conhecidos de vocês passem por aqui e deixem a opinião deles, isto vai ser bastante importante para mim.

Você gostou deste post? Compartilhe:

Tags: , , ,

28 Feb 11 Scala: Compreensão de listas e mapas


Agora que já aprendemos o básico da linguagem Scala, aprendemos um pouco sobre o suporte da linguagem a orientação a objetos, e um pouco sobre closures e operators em Scala. Agora vamos aprender um pouco sobre coleções em Scala, e como estas coleções estão realmente integradas na linguagem, com contrutores facilitados, e vamos também ver como a possibilidade de utilizar métodos como operadores pode se integrar perfeitamente a todos estes recursos na linguagem.

Listas

Em scala, instanciar listas de diversos tipos é bastante fácil, principalmente por causa dos factory methods definidos como objetos estáticos no pacote scala, vamos ver alguns exemplos.

samples/017_lists.scala

1
2
3
4
5
6
7
8
9
10
var l = List(1,2 ,3)
var l1 = 4 :: 5 :: 6 :: Nil
var l2 = l1 :: l;
var r1 = 1 until 10
var r2 = 1 to 10
println(l.toString)
println(l1.toString)
println(l2.toString)
println(r1.toString)
println(r2.toString)

Podemos ver na linha 1 a utilização do factory method “List” para criar uma instância de List[Int]
Na linha 2, estamos utilizando o operador :: também chamado de “cons” para concatenar itens no início de uma linha, este operador utiliza um truque da linguagem scala, todos os operadores que começam com “:” são considerados operadores do elemento a direita, e não do elemento a esquerda como normalmente acontece, deste forma, o método foi inicialmente chamado em “Nil”, que também representa uma lista vazia, e apartir dai o método foi chamado na lista criada.
Na linha 3, utilizamos o mesmo operador para criar uma lista maior, acontece a mesma coisa que na linha anterior, mas neste ponto já é claro que estamos concatenando duas listas.
Nas linhas 4 e 5 estamos utilizando os métodos “until” e “to” da classe “Int” para criar dois objetos do tipo “Range”, coloquei estes intervalos neste exemplo por que eles compartiham muitas das propriedades de List que quero mostrar mais adiante, o método until não inclui o último elemento na lista, o método to sin.
As linhas 7 a 12 imprimem o resultado no console quando executamos o programa como um script Scala.

Podemos fazer algumas coisas interessantes com listas, olhem estes exemplos de “for” em Scala.

samples/018_for.scala

1
for(i <- 1 to 10 ; if i % 2 == 0 ) println(i)

O que esta sendo feito neste “for”, é atribuimos a “i” um “Range” de 1 a 10, e o for só vai executar o bloco correspondente quando o número for par, ou seja, esta linha de código imprime todos os números pares.

Para tirar mais vantagens das listas, precisamos conhecer os métodos disponíveis, vamos dar uma olhada na tabela abaixo:




Método O que ele faz
List() Cria uma instância vazia de List
Nil Cria uma instância vazia de List
List("Cool", "tools", "rule") Cria uma nova instância de List[String] com os valores "Cool", "tools", e "rule"
val thrill = "Will" :: "fill" :: "until" :: Nil Cria uma nova List[String] com os valores "Will", "fill", e "until"
thrill(2) Retorna o segundo elemento (com baze zero) da lista thrill (retorna "until")
thrill.count(s => s.length == 4) Conta quantas Strings na lista tem comprimento igual a 4 (retorna 2)
thrill.drop(2) Retorna uma nova lista sem os dois primeiros itens
thrill.dropRight(2) Retorna uma nova lista sem od sois últimos itens
thrill.exists(s => s == "until") Verifica se existe na lista um elemento igual a “until”
thrill.filter(s => s.length == 4) Retorna uma nova lista contendo apenas os elementos com tamanho igual a 4
thrill.forall(s => s.endsWith("l")) Verifica se todos os elementos na lista terminam com a letra “l”(retorna true)
thrill.foreach(s => print(s)) Executa o método “print” para cada um dos itens da lista
thrill.foreach(print) O mesmo que o anterior, mas de forma mais concisa, passa cada um dos elementos da lista como parâmetro para o método print
thrill.head Retorna o primeiro elemento da lista
thrill.init Retorna uma lista com todos os elementos menos o último
thrill.isEmpty Verifica se a lista esta vazia
thrill.last Retorna o último elemento da lista
thrill.length Retorna o número de elementos da lista
thrill.map(s => s + "y") Retorna uma nova lista contendo os itens resultantes da operação especificada, no caso, adicionar “y” a cada um dos itens da lista
thrill.remove(s => s.length == 4) Retorna uma nova lista contendo todos da primeira, exceto os que tem o comprimento 4
thrill.reverse Retorna uma nova lista com a ordem inversa
thrill.
sort((s, t) =>
s.charAt(0).toLowerCase <
t.charAt(0).toLowerCase)
Retorna uma nova lista ordenada alfabeticamente consideando apenas a primeira letra de cada palavra
thrill.tail Retorna uma nova lista contendo todos menos o primeiro elemento

Com estes métodos, junto com os outros recursos da linguagem, podemos ver por que listas são uma das estruturas de dados mais importantes na linguagem Scala.

Mapas

Mapas em scala diferentes das listas que são sempre imutáveis, podem ser mutáveis ou imutáveis, no exemplo abaixo podemos ver a utilização de um mapa mutável.

samples/019_maps.scala

1
2
3
4
5
6
7
import scala.collection.mutable.HashMap
 
val treasureMap = new HashMap[Int, String]
treasureMap += 1 -> "Go to island."
treasureMap += 2 -> "Find big X on ground."
treasureMap += 3 -> "Dig."
println(treasureMap(2))

Podemos também criar mapas imutáveis, utilizando a sintaxe simplificada da linguagem como podemos ver no exemplo abaixo.

samples/020_maps.scala

1
2
val romanNumeral = Map(1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V")
println(romanNumeral(4))

Mapas podem ser bastante úteis também, mas no código Scala que eu lí até hoje ví muito poucos mapas.

Uma coisa a se notar, é que o contrutor de um mapa em scala recebe uma lista de tuples de dois elementos, e que a sintaxe “KEY -> VALUE” é apenas açucar sintático para (KEY, VALUE), e que poderiamos ter feito o exemplo anterior da seguinte forma:

samples/021_maps.scala

1
2
val romanNumeral = Map((1, "I"), (2, "II"), (3, "III"), (4, "IV"), (5, "V"))
println(romanNumeral(4))

A classe Map também tem diversos métodos úteis, mas como eu não costumo ver muitos mapas no código, não acho que valha a pena adicionar mais uma tabela aqui neste post, mas vocês podem dar uma olhada na documentação da classe Map na API online da linguagem.

Observações gerais

Uma das coisas mais importantes neste post é perceber que a grande maior parte dos métodos das listas recebe Closures como parâmetro, ou seja, o conhecimento adquirido até agora tem que ser utilizado em conjunto, o método List.filter recebe uma função como parâmetro, esta função por sua vez tem um parâmetro, e quando ela retorna true o elemento atual passa para a nova lista, quando retorna false o elemento é excluido da nova lista.

Outra coisa importante é que qualquer tipo de coleção ou iterator pode ser utilizado em um for, isto inclui Mapas, cada iteração do loop vai trabalhar com um Tuple.

No próximo post sobre Scala vamos voltar a falar sobre programação funcional, e logo depois sobre tipos parametrizados, upper e lower type bounds e coisas do gênero.

Como sempre, se tiver dúvidas, sugestões ou críticas, por favor deixe um comentário.

E para incentivar os comentários agora, deixo algumas perguntas:

  • você já esta utilizando Scala? Pensa em usar?
  • Esta feliz com a linguagem Java?
  • Gostarai de alguns dos recursos da linguagem Scala na linguagem Java?
  • Gostaria de utilizar Scala junto com outras bibliotecas Java?
  • Sabe como fazer isto? Tem dúvidas?
Você gostou deste post? Compartilhe:

Tags: , , ,

24 Feb 11 Testando aplicações web genéricas com Cucumber e Watir


Voltando a falar de cucumber para testar aplicações web genéricas, vamos falar agora sobre outra biblioteca ruby que permite automatizar a iteração com browsers, desta vez vamos falar do Watir, acho que foi a primeira biblitoeca ruby par automação de browsers que eu conheci, e era a opção default para mim até eu conhecer o Capybara integrado com o WebDriver que vimos antes, mas não por que eu ache a combinação muito melhor do que o Watir, simplesmente por que é a combinação padrão do Rails, o que facilita bastante a minha vida nas aplicações Rails, mas na maior parte dos casos isto não faz muita diferença.

Ok, mas por que se preocupar então em aprender outra solução? Simplesmente por que nenhuma solução é perfeita, em algum momento a combinação Capybara + WebDriver não vai atender as suas necessidades, e você vai precisar utilizar o WebDriver diretamente, ou o Watir, já que o Capybara utiliza estes, e não expões todos os recursos deles por exemplo.

Configuração do projeto e do ambiente

Como nos outros tutoriais, vamos começar criando a estrutura básica, vamos criar um diretório de nome “cucumber_watir”, e dentro dele vamos criar um arquivo Gemfile e um Rakefile como nos exemplos abaixo:

cucumber_watir/Gemfile

1
2
3
source 'http://rubygems.org'
gem 'cucumber'
gem 'watir'

Neste Gemfile, precisamos apenas do Cucumber e do Watir, não vou falar da parte de banco de dados, acho que vou escrever um post exclusivamente sobre isto mais adiante, mas por enquanto vou colocar apenas comentários sobre qual operação aquele ponto do código deveria realizar no banco de dados.

cucumber_watir/Rakefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
require "rubygems"
require "bundler/setup"
Bundler.require
require 'cucumber/rake/task'
 
namespace :cucumber do
  Cucumber::Rake::Task.new(:ok, 'Run features that should pass') do |t|
    t.cucumber_opts = "features --format pretty"
  end
  Cucumber::Rake::Task.new(:wip, 'Run features that are being worked on') do |t|
    t.cucumber_opts = " --format pretty --tags @wip:3 --wip features"
  end
  Cucumber::Rake::Task.new(:rerun, 'Record failing features and run only them if any exist') do |t|
    rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
    rerun_opts = rerun.to_s.strip.empty? ? "--format pretty features" : "--format pretty #{rerun}"
    t.cucumber_opts = "#{rerun_opts} --format rerun --out rerun.txt --strict --tags ~@wip"
  end
  desc 'Run all features'
  task :all => [:ok, :wip]
  desc 'Setup the basic directories and files'
  task :setup do
    require 'fileutils'
    ["features", "features/support", "features/step_definitions"].each {|f| FileUtils.mkdir_p f }
    File.open("features/support/env.rb","w") do |env_rb|
      env_rb.write <<_EOF_
require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support
 
Before do
  #actions before each scenario
end
_EOF_
end
    end
end
 
desc 'Alias for cucumber:ok'
task :cucumber => 'cucumber:ok'
 
task :default => :cucumber

Este Rakefile é o padrão que temos utilizado nos últimos posts, sem nenhuma alteração, copiado do projeto do Capybara e adicionada a task de setup do ambiente que criei para o post sobre Java.
Agora com a infra configurada, podemos finalizar o setup do projeto com os comandos abaixo, executados no console dentro do diretório cucumber_watir.

1
2
  bundle install
  rake cucumber:setup

Agora podemos começar a trabalhar, acredito que como a idéia é testar uma aplicação web qualquer, podemos utilizar o mesmo requisito de lista de tarefas utilizado nos exemplos com Rails e com Capybara, então estou copiando o mesmo arquivo .feature, o código esta abaixo:

cucumber_watir/features/task_list.feature

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
Feature: Task List
    In order to remember what I need to do and what I have already completed
    As a professional
    I want to create a list of tasks, add tasks to the list and mark as completed the tasks I have completed
 
    Scenario: Add task
        Given I am on my task list page
        And I fill the task field with "my new task"
        When I click "Add"
        Then I should see "my new task" on the task list
 
    Scenario: List tasks
        Given I am on my task list page
        And the following tasks exists:
            |title        |done |
            |my task      |true |
            |other task   |false|
            |one more task|false|
        And I refresh the page
        Then I should see the folloring tasks:
            |Task         |Done |
            |my task      |Done |
            |other task   |Done |
            |one more task|Done |
        And the line with text "my task" should have the class "done"
 
    Scenario: Mark task as completed
        Given I am on my task list page
        And the following tasks exists:
            |title        |done |
            |my task      |false|
            |other task   |false|
            |one more task|false|
        And I refresh the page
        When I click on "Done" at "other_task_line"
        Then the line with text "other task" should have the class "done"
 
    Scenario: Duplicate completed task
        Given I am on my task list page
        And the following tasks exists:
            |title        |done |
            |my task      |true |
            |other task   |false|
            |one more task|false|
        And I refresh the page
        And I fill the task field with "my task"
        When I click "Add"
        Then I should see the folloring tasks:
            |Task         |Done |
            |my task      |Done |
            |other task   |Done |
            |one more task|Done |
        And I should see "has already been taken"
 
    Scenario: Duplicate open task
        Given I am on my task list page
        And the following tasks exists:
            |title        |done |
            |my task      |true |
            |other task   |false|
            |one more task|false|
        And I refresh the page
        And I fill the task field with "other task"
        When I click "Add"
        Then I should see the folloring tasks:
            |Task         |Done |
            |my task      |Done |
            |other task   |Done |
            |one more task|Done |
        And I should see "has already been taken"

Se executarmos o cucumber agora, ele vai informar que não sabe como executar nenhum dos passos especificados, e vai imprimir na tela o esqueleto dos métodos que devem ser implementados em Ruby, podemos ver abaixo este esqueleto de métodos gerado pelo cucumber:

cucumber_watir/cucumber_steps.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Given /^I am on my task list page$/ do
  pending # express the regexp above with the code you wish you had
end
 
Given /^I fill the task field with "([^"]*)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end
 
When /^I click "([^"]*)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end
 
Then /^I should see "([^"]*)" on the task list$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end
 
Given /^the following tasks exists:$/ do |table|
  # table is a Cucumber::Ast::Table
  pending # express the regexp above with the code you wish you had
end
 
Given /^I refresh the page$/ do
  pending # express the regexp above with the code you wish you had
end
 
Then /^I should see the folloring tasks:$/ do |table|
  # table is a Cucumber::Ast::Table
  pending # express the regexp above with the code you wish you had
end
 
Then /^the line with text "([^"]*)" should have the class "([^"]*)"$/ do |arg1, arg2|
  pending # express the regexp above with the code you wish you had
end
 
When /^I click on "([^"]*)" at "([^"]*)"$/ do |arg1, arg2|
  pending # express the regexp above with the code you wish you had
end
 
Then /^I should see "([^"]*)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end

Como podemos ver, são mais passos a implementar do que os necessários quando implementamos os testes com a aplicação Rails, a explicação para isto é que o Rails já provê alguns passos padrão já implementados no arquivo web_steps.rb, mas como estamos utilizando algo diferente do padrão do Rails temos que implementar estes passos também.

Para facilitar a implementação destes passos, vamos primeiro estudar um pouco sobre a API do Watir que vamos utilizar mais adiante.

O Watir segundo a documentação dele, roda em Windows, Linux e Mac, e consegue controlar os browsers Internet Explorer, Firefox, Safari, Chrome e Opera. Claro que nem todas as combinações entre Browser e OS são possíveis.

Watir API

Trabalhando com o Watir, normalmente estaremos trabalhando com uma instância da classe Watir::Browser, então vamos começar com um overview dos métodos mais úteis desta classe:

Código Explicação
Começando
browser = Watir::Browser.new Abre uma nova janela do browser
browser = Watir::Browser.start(“http://google.com”) Abre uma nova janela do browser no endereço especificado
browser.goto(“http://amazon.com”) Abre o endereço no browser
browser.close Fecha a janela do browser
Acessando elementos
t = browser.text_field(:name, “username”) Busca um campo de texto
b = browser.button(:value, “Click Here”) Busca um boão pelo titulo
d = browser.select_list(:name, “month”) busca um combo box
c = browser.checkbox(:name, “enabled”) busca um checkbox
r = browser.radio(:name, “payment type”) busca um radio
f = browser.form(:name, “address”)
f = browser.form(:action, “submit”)
Busca um formulário
l = browser.link(:url, “http://google.com”)
l = browser.link(:href, “http://google.com”)
Busca um link
td = browser.table(:name, ‘recent_records’)[2][1] Uma tabela, depois a segunda linha primeira coluna da mesma
Lendo valores
browser.html
e.html
O HTML interno do browser ou de qualquer elemento
browser.text
e.text
O texto do browser ou qualquer elemento
browser.title O título da janela
browser.status
=> “Done”
O Status do browser
browser.text.include? ‘llama’ Verifica se a string especificada existe na página
browser.table(:id, ‘recent_records’).to_a O conteúdo da tabela como um array
Manipulando Elementos
b.click
l.click
Simular um clique em um botão ou link
t.set(“mickey mouse”) Setar o valor de um campo texto
t.set(“line 1\nline2″) SSetar o valor com mais de uma linha
c.set
r.set
Selecionar um checkbox ou radio
t.clear
c.clear
r.clear
Limpar o valor de um item
d.select “cash”
d.set “cash”
Selecionar um item em um combo box
d.clearSelection Limpar a seleção de um combo box
f.submit Enviar um formulário

Misturando o watir com o cucumber

Primeiro devemos editar o env.rb para adicionar o require do watir, como podemos ver abaixo.

cucumber_watir/features/support/env.rb

1
2
3
4
5
6
7
8
require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support
require 'watir'
browser = Watir::Browser.new
@browset = browser
 
Before do
  #aqui é interessante limpar o banco de dados
end

Perceba que nas linhas 3 e 4 estamos inicializando a variável browser que sera utilizada no resto do script.

Depois, podemos passar a implementar os passos para o cucumber, vamos dar uma olhada no código do script pronto abaixo:

cucumber_watir/features/step_definitions/cucumber_steps.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Given /^I am on my task list page$/ do
  browser.open('http://localhost:3000/')
end
 
Given /^I fill the task field with "([^"]*)"$/ do |value|
  browser.text_field(:name, 'task_field').set(value)
end
 
When /^I click "([^"]*)"$/ do |value|
  browser.button(:text,value).click
end
 
Then /^I should see "([^"]*)" on the task list$/ do |value|
  raise 'error' unless browser.table(:name,'tasks_list').text.incluse? value
end
 
Given /^the following tasks exists:$/ do |table|
  #create the tasks on the database
end
 
Given /^I refresh the page$/ do
  browser.open('http://localhost:3000/?')
end
 
Then /^I should see the folloring tasks:$/ do |table|
  raise 'error' unless browser.table(:name,'tasks_list').to_a == table.raw
end
 
Then /^the line with text "([^"]*)" should have the class "([^"]*)"$/ do |text, class|
  raise 'error' unless browser.row(:class,class).text.include? text
end
 
When /^I click on "([^"]*)" at "([^"]*)"$/ do |button, place|
  browser.row(:id,place).button(:value,button).click
end
 
Then /^I should see "([^"]*)"$/ do |text|
  raise 'error' unless browser.text.include? text
end

Podemos ver que as implementações são bem simples, mas a navegação entre os elementos da página é bem mais limitada que no Capybara.

Outra diferença bastante gritante é a falta de uma DSL como a do capybara, nada que seja muito complexo de ser criado, mas não esta pronto, mas uma vantagem é a possibilidade de interagir diretamente com o browser, ter acesso ao objeto COM que representa o Internet Explorer por exemplo, pode ser útil em algumas situações, precisei disto para automatizar uma aplicão Adobe Flex a algum tempo.

Na próxima semana vou dar uma folga ao cucumber, e vou falar um pouco do SpecFlow para testes de aplicações escritas em .NET, mas o conhecimento no Watir vai nos ajudar nisto, já que pretendo mostrar ele integrado ao WatirN.

Uma pergunta para quem leu até aqui, vocês gostaram do formato de Cheat Sheet apresentado neste post?

Como sempre, se tiverem dúvidas quanto ao post, sugestões ou críticas podem deixar um comentário que ele sera sempre bem vindo.

Falando nisto, se quiserem algum exemplo mais especifico sobre o cucumber ou sobre BDD, podem deixar um comentário ou mandar pelo formulário de contato do blog que tento responder no próximo post, ou até mesmo com um post especial para responder a pergunta.

Você gostou deste post? Compartilhe:

Tags: , , , , , ,

21 Feb 11 Scala: Closures, Operadores e DSLs


Depois de uma breve introdução a linguagem Scala, e de falar sobre orientação a objetos em Scala, agora vamos falar um pouquinho de closures em Scala, mas não apenas de closures, de blocos de código, closures automáticas, e vamos começar a falar de alguns dos recursos funcionais da linguagem, como Higher-Order functions, funções como valor e passar funções como parâmetros para outras funções.
Veja bem, este post não vai ser uma introdução a programação funcional em scala, pretendo fazer isto mais adiante, mas aproveitando o gancho das closures, como alguns dos conceitos do que vou falar são bem parecidos, já vamos também estudar alguns dos conceitos básicos que permitem programação funcional com a linguagem scala.

Então sem mais delongas, vamos a alguns conceitos:
Closure é uma função de primeira classe com variáveis livres, ligadas ao contexto onde a closure foi criada.
Lendo esta definição, já que closure é uma função de primeira classe, quer dizer que Scala possui funções de primeira classe.
Funções de primeira classe são funções tratadas como objetos, que podem ser armazenados em estruturas de dados, ser passados como parâmetros para outras funções e podem ser o valor de retorno para outras funções.
Lendo esta definição de funções de primeira classe, ou first-class functions, uma parte desta definição me lembra da definição de Higher-Order functions, veja abaixo:
Higher-Order Functions são funções que recebem uma ou mais funções como parâmetros e/ou retornam uma função.
Juntando tudo isto, e partindo do principio que Scala suporta Closures podemos também inferir que scala suporta higher-order functions, uma caracteristica bastante comum de linguagens de programação funcionais.

Voltando as Closures

Vamos começar com um exemplo, para facilitar as expicações:

samples/013_closure.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
object ClosureApp {
  def printResult(func : (Int) => Any) {
    println(func(5))
  }
  def main(args : Array[String]){
    var i = 5
    val funcParam = (j : Int) => j + i
    printResult(funcParam)
    i = 6
    printResult(funcParam)
  }
}
ClosureApp.main(args)

Na linha 2, o método printResult precisa receber como parâmetro uma função que receba um Int como parâmetro e retorne qualquer coisa.
Na linha 3 chamamos esta função passando 5 como parâmetro e passamos o resultado dela para o println.
Se você lêr novamente a definição de higher-order function, esta função definida na linha 2 é um exemplo bem simples de uma, até agora temos uma higher-order function, mas nenhuma closure.

Na linha 7, definimos uma variável “funcParam” que aponta para uma função anonima, que recebe um Int e retorna um Int.
Esta é uma função de primeira classe, estamos armazenando uma função em uma variável, que poderia ser parte de uma estrutura de dados, e na linha 8, estamos passando esta função armazenada em uma variável como parâmetro para outra função.
Mas o que define esta função anonima declarada na linha 7 como uma closure, alem de ser uma função de primeira classe, é que ela acessa uma variável local do método onde ela foi definida, dentro da closure, é armazenada uma referência para a variável definida no mesmo contexto onde esta closure foi criada, e quando a closure é executada ela lê o valor atual da variável, e não o valor que esta variável tinha quando a closure foi criada. Ou seja, mesmo sendo executada em um lugar totalmente diferente, a closure é executada no contexto onde ela foi declarada.

Isto pode parecer um pouco complexo, mas com o tempo vai ficando bem simples.

Criação atomática de Closures

Scala pode criar closures automaticamente quando a assinatura do método pede uma closure como parâmetro, e como já vimos antes que qualquer método pode ser utilizado como um infix operator, vamos juntar estas duas informações, e re-criar o loop while do Java. Veja o exemplo abaixo:

samples/014_whileloop.scala

1
2
3
4
5
6
7
8
9
10
11
def whileLoop(cond: => Boolean)(body: => Unit): Unit = {
  if (cond) {
    body
    whileLoop(cond)(body)
  }
}
var i = 10
whileLoop (i > 0) {
  println(i)
  i -= 1
}

Na linha 1, definimos o método “whileLoop”, este método recebe dois parâmetros, o primeiro parâmetro é uma função que não tem parâmetros e retorna um valor boolean, a forma de declarar é um caso especial do que acabamos de ver sobre funções anonimas, se a função anonima não precisa de parâmetros podemos omitir os parenteses e utilizar a sintaxe “: => “, como visto em “(cond: => Boolean)”, e o segundo parâmetro é uma função sem parâmetros e sem retorno, e a função whileLoop também não tem retorno (ambas retornam Unit que pode ser um bloco de código ou então o equivalente a void no java).
Dentro da função, se “cond” retornar true, chama body, e chama o método novamente, como ambos “cond” e “body” são closures, e tem acesso as variáveis declaradas no escopo onde foram criadas, depois de executar body uma vez, o retorno de cond pode ter sido alterado.
Se olharmos entre as linhas 7 e 11, “cond” é a expressão “(1 > 0)” e body o bloco de código que segue, ambas as closures foram passadas como parâmetro para o método whileLoop.

Podemos brincar um pouco mais com a idéia de que qualquer método pode ser utilizado como um infix ou postfix operator, e com a criação de closres dinâmicas criando um loop um pouco mais flexível, vamos analisar o seguinte exemplo:

samples/015_loop.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class LoopUnlessCond(body: => Unit) {
  def unless(cond: => Boolean) {
    body
    if (!cond) unless(cond)
  }
}
 
def loop(body: => Unit): LoopUnlessCond = new LoopUnlessCond(body)
 
var i = 10
loop {
  println("i = " + i)
  i -= 1
} unless (i == 0)

Entre as linhas 1 e 6, definimos a classe LoopUnlessCond que recebe uma função sem parâmetros e que retorna Unit como parâmetro no construtor.
Nesta classe, entre as linhas 2 e 5 definimos um método nesta classe, que recebe uma função sem parâmetros e que retorna Boolean como parâmetro, neste método executamos o método “body” recebido como parâmetro no construtor da classe, e logo depois, se cond retornar false, executamos o método unless novamente.
Mais adiante na linha 8 definimos um método chamado “loop” que recebe como parâmetro uma função sem parâmetros que retorna Unit, este método retorna uma nova instância da classe “LoopUnlessCond” que acabamos de criar, e passa o método recebido como parâmetro para o contrutor desta classe.
Na linha 11, chamamos o método loop, passando a closure definida nas linhas 12 e 13. Como o retorno do método “loop” é uma instância da classe “LoopUnlessCond”, na linha 14 podemos chamar o método unless definido nesta classe, passando mais uma closure como parâmetro para este método.
Juntando a possibilidade de utilizar qualquer método como um infix ou postfix operator, e a criação atomática de closures, podemos ter bastante flexibilidade na criação de pequenas DLSs, e como estas são apenas classes e métodos Scala, podemos empacotalas em bibliotecas e distribuílas com bastante facilidade.

Vamos dar uma olhada no último exemplo deste post:

samples/016_loop.scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
object ExceptionHolder {
  class BreakException extends Exception {}
  val breakException = new BreakException
}
def break = throw ExceptionHolder.breakException
 
def loop(body: => Unit): Unit = {
  try{
    body
    loop(body)
  }catch{
    case e: ExceptionHolder.BreakException => return
  }  
}
 
var i = 10
loop {
  if(i == 0) break
  println("i = " + i)
  i -= 1
}

Entre as linhas 1 e 4, criamos um objeto com uma única variável armazenando uma instância de uma excessão (BreakException).
Na linha 5 criamos um método “break” que a única responsabilidade é disparar uma excessão do tipo “BreakException” utilizando aquela variável do objeto definido.
Entre as linhas 7 e 14 criamos um método “loop” que recebe um bloco de código que retorna Unit como parâmetro, este método fica chamando o bloco até que uma excessão do tipo BreakException seja disparada.
E para terminar, entre as linhas 17 e 21 utilizamos esta nova construção da linguagem que acabamos de criar.

Como podemos ver neste post, a combinação de closures e métodos como operadores é mais do que o suficiente para criarmos novas construções para a linguagem, e como estas novas construções não passam se classes e métodos Scala, podemos compartilha-los entre projetos como bibliotecas “.jar” por exemplo.
Por enquanto estamos criando construções bastante simples, no próximo artigo vamos estudar um pouco sobre tipos genéricos em Scala, depois vamos estudar um pouco sobre programação funcional, mas em um dos próximos artigos vamos voltar a estudar sobre a criação de DSLs mais complexas em Scala.
Espero que agora vocês estejam começando a gostar um pouco mais da linguagem.

Como sempre, se tiverem dúvidas, comentários, sugestões ou criticas, por favor deixem nos comentários.

Você gostou deste post? Compartilhe:

Tags: , , ,