Mostrando postagens com marcador Java. Mostrar todas as postagens
Mostrando postagens com marcador Java. Mostrar todas as postagens

10 de abril de 2014

Segundo dia de QConSP 2014

O segundo dia de QConSP 2014 começou com duas keynotes fantásticas. O primeiro a falar foi um membro do Jet Propulsion Lab da NASA: Rob Witoff, seguido por Jarrod Overson, que trabalha na RIOT Games (a empresa por trás de League of Legends). Depois começaram as tracks com palestras simultâneas, escolhi participar das que tinham algum relacionamento direto com desenvolvimento. Post rápido!

Building a Data Science Program at NASA/JPL with Visual Analytics

O palestrante é um data scientist que trabalha na NASA. Fez uma analogia interessante, em resumo do que minha memória permite: "uma vasta porção do nosso universo é dark matter, que nós ainda não entendemos; da mesma forma os dados que nós temos e não entendemos são dark data; podemos ter muito conhecimento interessante escondido nos nossos silos de armazenamento de dados".

Ele elencou passos importantes para explorar a dark data:

  1. Liberate data: consistem em liberar este dado escondido e torná-lo acessível. Eles fizeram muito nisso e tornaram vários dados acessíveis através de APIs Web públicas, i.e. torne fácil de acessar os dados, construa uma camada em cima deles, esqueça strings de conexão cabulosas para acessar os repositórios.
  2. Enable engineers: torne possível que as pessoas possam interagir com os dados, que eles consigam fazer o que precisa ser feito: filtrar, combinar, analisar, etc.
  3. Infuse data science: coloque data science em tudo isso, correlacione os dados, encontre padrões.
  4. Innovate: agora você já tem tudo que precisa para inovar, então vá e inove! Mostre estes dados de formas diferentes e tire conclusões sobre eles, construa camadas em cima de camadas e continue inovando.

A NASA possui um negócio bem interessante chamado "innovation vacation" que consiste em tirar "férias" para inovação. Um profissional tem uma ideia; a ideia é boa; este profissional irá tirar "férias para inovação" para construir a ideia junto com o pessoal de inovação, são no máximo duas semanas em que a pessoal fica completamente isolada do resto da empresa: sem acesso a e-mails, sem reuniões, etc; o time do palestrante faz uma espécie de coaching deste profissional durante este período; este tempo possui três "marcos" fundamentais: alinhamento, demo e entrega, i.e. é feito um alinhamento inicial da ideia e das expectativas, depois de um certo trabalho é feito uma demonstração para ver se está indo no caminho certo e no final do ciclo é feita a entrega e fim, não há adiamentos ou prolongamentos.

Dois slides desta apresentação me deixaram muito feliz, que fala como funciona a parte de inovação da NASA e que apoiam o que escrevi no post sobre otimização prematura de negócio e com alguns comentários do post sobre o primeiro dia de QCon:

  • Fail fast. Iterate.
  • Run like a startup

Scaling League of Legends: managing culture, extreme complexity and 30 million active users

Achei que esta apresentação seria mais técnica, mas falou do problema de escalar pessoas, processos, etc. Comentou que o quê funciona no "pequeno" não funciona no "grande". A apresentação trouxe a Web como exemplo de algo que escalou muito bem e o apresentador afirma que ela escalou porque ela precisava escalar de um jeito ou de outro e podemos aprender com ela:

  1. Ela precisa ser distribuída, pessoas do mundo inteiro irão acessar conteúdos espalhados por todo lugar.
  2. Precisa ser assíncrona e levar em consideração timezones diferentes.
  3. Todos são iguais, não importa se você tem doutorado ou ainda está no colégio, você tem o direito de ver e publicar conteúdo.

Ele relaciona estes pontos da Web para tornar uma empresa escalável: você precisa ter formas de comunicação assíncronas, reuniões não são escaláveis; você precisa ter uma maneira de colaboração, em que todos são ouvidos; você precisa lidar com times remotos; etc. (A parte em ênfase foi a que achei top da balada.)

Depois ele entrou mais em detalhes da parte Web, mostrando que ela passou a ser um target de compilação válido. Unity está compilando jogos para rodar na web; Asm.js está fazendo o Javascript ser mais nativo/rápido; Web Components estão aí; HTTP2.0 irá revolucionar a forma de trabalho. Achei interessante, pois depois relacionei isto com as demais apresentações e é notável de que tudo parece estar com uma perna nas tecnologias fundamentais para a Web (HTML, CSS, JS). O que me fez ver mais valor ainda em iniciativas como o Haste (compilador Haskell com target para Javascript).

Track talks

Vou tentar resumir as talks das tracks, pois não achei nenhuma que merecesse destaque.

Programação funcional reativa: Lidando com código assíncrono: Interessante, pelo menos ele falou que gosta de Haskell. Mostrou como funciona o RxJS, um framework com grande influência de FRP (Functional Reactive Programming) para Javascript.

JavaScript Funcional: Bem didática e boa abordagem, porém muito introdutório, aproveitei pouco, exceto por ter conhecido o link FunctionalTalks.org.

Uma startup em Scala: Dos patterns à produção: Muita introdução, mais do mesmo. Ver código Scala sempre me dá vontade de vomitar. É verborrágico de mais. O pessoal se gaba do type-system, mas o negócio leva décadas para compilar. Me gusta Haskell, great type system, great compilation time.

C# e C++/CX - O que há de novo nas linguagens para suportar o desenvolvimento de aplicações modernas: Esta trouxe uma visão legal da unificação que a Microsoft está fazendo em suas quatro plataformas: Smartphone, Tablet, Desktop e XBox. A interoperabilidade que ela está fazendo me pareceu fantástica: código C++/CX chamado de Javascript e vice-versa, C# no meio, novos conceitos de "aplicações modernas".

Building a Cloud IDE you might not hate: the Orion experience and beyond: Comentou sobre a construção da IDE na nuvem chamada Orion e mostrou alguns concorrentes, nada muito interessante até o momento, apenas mostra que existe um movimento forte de várias empresas e iniciativas da comunidade para uma IDE na nuvem.

5 de agosto de 2013

Infectado por exceções

Acredito que códigos infectados por exceções sejam ruins. Imagine o seguinte código inofensivo:

public static Foo newFoo(int bar) {
  switch (bar) {
    case 1: 
      return new FooOne();

    case 2: 
      return new FooTwo();

    default: 
      throw new IllegalArgumentException("invalid bar code: " + bar);
  }
}

Você utiliza ele no meio da sua regra de negócio, com a maior tranquilidade. O código compila e tudo:

public static List<BazFoo> getAllBazFoo() {
  List<BazFoo> result = new ArrayList<>();
  List<Baz> allBaz = BazDAO.getAllBaz();
  for (Baz baz : allBaz) {
    int id = baz.getId();
    int bar = baz.getBar();
    Foo foo = Foo.newFoo(bar);
    BazFoo e = new BazFoo(id, foo);
    result.add(e);
  }
  return result;
}

O problema é que a chamada Foo.newFoo(bar) pode lançar um IllegalArgumentException e quando isto ocorrer você não terá pista nenhuma de qual Baz está com a informação inválida.

Para tratar isto e tornar a mensagem mais útil, temos que lembrar de adicionar um try/catch no código:

public static List<BazFoo> getAllBazFoo() {
  List<BazFoo> result = new ArrayList<>();
  List<Baz> allBaz = BazDAO.getAllBaz();
  for (Baz baz : allBaz) {
    int id = baz.getId();
    int bar = baz.getBar();
    Foo foo;
    try {
      foo = Foo.newFoo(bar);
    } catch (IllegalArgumentException e) {
      throw new IllegalArgumentException("invalid baz: " + id, e);
    }
    BazFoo e = new BazFoo(id, foo);
    result.add(e);
  }
  return result;
}

Adicione várias camadas de abstração à sua aplicação e pronto: espagueti sabor excepcional. Ao adicionar exceção em algum ponto, você terá que revisar toda a pilha de abstrações que você tem e garantir que a exceção não faça mal a ninguém e que tenha uma mensagem útil para futuras manutenções.

Haskell resolve boa parte da necessidade de exceções com retornos que simbolizam falhas, e.g. Maybe e Either, e o uso de Monad elimina a necessidade de verificar os retornos a cada passo.

Porém, ainda existem exceções em Haskell, por quê? Não consigo entender o motivo para preferir exceções ao invés de retornos especiais. Em quais situações usar exceções é melhor? Mais me parecem um goto modernizado.

5 de junho de 2013

Abrace Android

Já faz um tempo que não escrevo neste blog. As minhas aventuranças no mundo Haskell pararam um pouco depois do último post que fiz ("C, C++, Java, Haskell?").

Dediquei um bom pedaço deste tempo ausente para tentar fazer um jogo em C. Tive um bom começo: fiz um personagem se movimentar, pular e colidir com outros objetos. Comecei a perceber onde estava me metendo quando estas perguntas surgiram na minha mente:

  • Como vou distribuir meu jogo?
  • Como vou lançar uma atualização?
  • Como vou comercializar?

Neste momento estava com uma conexão carinhosa com o projeto e foi difícil aceitar que estas dificuldades poderiam ser maiores do que meu esforço disponível. Passada esta fase de aceitação, decidi que precisava tirar todas estas preocupações da cabeça para conseguir me dedicar ao desenvolvimento do jogo. Para isto precisava de uma plataforma que me oferecesse todos os itens que coloquei na lista acima.

Android

Escolhi a plataforma Android por conseguir acesso a um ambiente de desenvolvimento de forma mais fácil, pois poderia continuar utilizando meu notebook rodando Windows para programar e realizar testes no meu celular Samsung Galaxy 5. Sem chance de investir em um ambiente para iOS no momento.

Para garantir que a plataforma atendia a todos os itens que listei acima, fiz o processo do começo ao fim:

  1. Criei um aplicativo de exemplo
  2. Testei no meu celular
  3. Publiquei o aplicativo (Como vou distribuir meu jogo?)
  4. Alterei algumas coisas e lancei atualizações para ele (Como vou lançar uma atualização?)
  5. Criei uma versão free com propagandas e uma paga sem propagandas (Como vou comercializar?)

O resultado disto tudo foi muito agradável. Agora não me restam dúvidas sobre o ambiente Android, basta continuar meu aprendizado na plataforma e o desenvolvimento do projeto em paralelo. Espero que em breve eu consiga postar no blog um link para o resultado do projeto publicado. :)

20 de janeiro de 2013

C, C++, Haskell, Java?

Estou empolgado e agoniado com meu aprendizado sobre OpenGL. Ao mesmo tempo em que aprender OpenGL me deixa muito satisfeito, também me deixa inquieto: dediquei muito tempo aprendendo Haskell e fui envolvido de uma forma a gostar muito da linguagem e da comunidade. Porém, fazer algo em OpenGL se mostrou muito difícil e até mesmo solitário. Existem pouquíssimos materiais sobre isso e a maioria deles estão obsoletos.

Tive que largar Haskell por um tempo e religar os neurônios da linguagem C para aprender o fluxo das versões mais modernas do OpenGL, com shaders e buffer objects. O motivo foi simples: não consegui fazer nada funcionar em Haskell e não tive certeza se a culpa era minha ou das bibliotecas que utilizava.

Pois bem, fazer uma câmera se movimentar em um mundo 3D em C também não foi nada fácil. Mas com a abundância de material, consegui deixar funcionando de uma forma que me deixou bem satisfeito.

Decidi então voltar para Haskell e aplicar este conhecimento que acabei de conquistar.

Que trabalheira! Fazer tudo se encaixar da maneira certa e com os tipos corretos me fez suar algumas camisas. Se não fosse tão teimoso, não teria conseguido. Tive que trocar o pacote OpenGL pelo OpenGLRaw pois o pacote OpenGL não tem binding para "glUniformMatrix*"; também troquei o GLFW pelo GLFW-b porque o primeiro dependia do pacote OpenGL.

Recentemente li um artigo no InfoQ questionando se a linguagem C ainda era útil. Me fez pensar em muita coisa, inclusive em abandonar definitivamente Haskell e voltar às origens: ponteiros, gerenciamento de memória manual, strings cabulosas, ...

Talvez seja porque sou muito novo neste mundo, mas me sinto meio improdutivo programando em Haskell. A impressão que tenho é que, a menos que seja algo muito bem consolidado e utilizado por muitas pessoas da comunidade, muito tempo será gasto atrás de bibliotecas que normalmente são apenas um binding para uma escrita em C. Quando não for isto, só para ter uma breve ideia do que a API de uma biblioteca faz, acabo lendo alguns papers relembrando o que são Functors e Monads (para entender Arrows acho que vou precisar comprar um livro!).

Entender ponteiros e gerenciamento de memória em C também foi difícil. Mas naquela época eu era um jovenzinho no mundo da programação, meu conhecimento era muito limitado, se baseava muito em bater no código até que ele fizesse o que eu queria. Agora não consigo mais aceitar fazer alguma coisa sem entender o que estou fazendo.

Eu lembro que tive bons motivos para trocar C por Java. Depois de passar um tempo utilizando Java, vejo também as partes negativas da linguagem. Meu trabalho de conclusão da graduação foi dedicado a evidenciar os motivos desta troca e do futuro promissor de linguagens como Haskell.

No final das contas, acredito que esta pseudo-decepção acontece com toda linguagem em que você dedica bastante tempo para aprendê-la. A propaganda e os primeiros passos são maravilhosos. Parece que você descobriu um novo mundo onde todos são ricos e podem desfrutar de todas as coisas boas. Com o tempo você percebe que não é bem assim: cada escolha que a linguagem fez significa pontos positivos e negativos para nós usuários. Foi assim com C, Python, Java e agora com Haskell.

Java escolheu uma abordagem orientada a objetos: mesmo que seja uma função estática ou uma variável global, será necessária a roupagem de uma classe para acomodar este código. Haskell escolheu o funcional puro: mesmo operações que envolvam I/O recebem a roupagem de um monad.

Não que isto seja ruim. A utilização de monads para modelar computação sequencial foi uma grande descoberta que proporcionou a criação de inúmeras soluções. Estas escolhas trazem pontos positivos e negativos. Só devemos ter cuidado com o purismo extremo para não criar aberrações (e.g. Singleton).

Enfim, estou agoniado pois não sei o que escolher:

  • C me parece flexível mas com as complexidades que não quero ter. C11 parece solucionar algumas coisas (e.g. <threads.h>), mas o GCC ainda não suporta;
  • Sou bem produtivo em Java mas o overhead obrigatório e a necessidade de ter uma JVM me irritam profundamente;
  • C++ ainda é uma incógnita para mim, se for para ir neste nível ainda prefiro utilizar C. Java já me fez ver os motivos para dizer não à O.O. Por outro lado, boa parte de quem utilizava C está indo para C++, inclusive o GCC está utilizando C++. As bibliotecas disponíveis para C++ também pesam bastante (e.g. GLM, Boost);
  • Haskell é minha linguagem da vez mas está começando a me incomodar um pouco, sentimento de improdutividade me deixa muito tenso. Talvez precise dedicar mais tempo para que isto passe. Não sei se este esforço irá valer a pena ou poderia ser melhor aproveitado aprimorando meus conhecimentos em outra linguagem.

A diferença de desempenho entre C e Haskell não me preocupa muito, pois não vou escovar bits para ter um bom desempenho tão cedo.

No final das contas, a opinião mais correta sempre é: depende do projeto, escolha a melhor linguagem para o seu projeto. Pois bem, não tenho projeto nenhum, mas quero me aprofundar no desenvolvimento de jogos.

E agora?

14 de julho de 2012

O futuro da programação

Postado originalmente em Seniortec.

De acordo com o índice TIOBE, em 1996 a linguagem de programação mais utilizada era C. Apenas dez anos mais tarde, em 2006, Java passou a ocupar o primeiro lugar. Por quê?

Com o baixo custo e o fácil acesso a grandes quantidades de memória, a transição de C para Java foi um reflexo natural dos programadores em busca de maneiras mais fáceis de gerenciar este recurso que crescia de maneira muito rápida.

Novamente estamos passando por uma quebra no paradigma computacional. A busca incansável por maior quantidade de ciclos é deixada de lado e da lugar à busca por paralelismo de operações e quantidade de núcleos de processamento.

As linguagens atuais estão preparadas para estas mudanças? Java ainda é bom o suficiente para manter sua hegemonia? Perguntas como estas começam a aparecer em artigos, fóruns, apresentações e congressos de nossa área.

Juntamente com estas perguntas, começa a surgir um interesse súbito por linguagens funcionais, como mostra a mensagem deste mês do índice TIOBE:

"Last month we asked ourselves the question what language could become the next big new programming language. We suggested several candidates such as Scala, Erlang and Clojure. Clearly, the new thing was expected to come from the functional programming field. A functional language not explicitly mentioned was Haskell. And this month it was Haskell that jumped from #35 to #25. Looking at the TIOBE trend graph of Haskell (starting in 2003) it shows a constant rise, with peaks in 2006, 2010 and now in 2012. This certainly sounds promising."

Todo este interesse não é preciosismo de alguns estudiosos. O paradigma funcional puro soluciona de uma forma muito suave a maioria dos problemas encontrados quando tratamos de assuntos de importância fundamental para esta nova arquitetura computacional: concorrência, paralelismo e escalabilidade.

A linguagem Haskell, por exemplo, oferece:

  • Estruturas de dados persistentes: seguras para ambientes concorrentes.
  • Operações que envolvem I/O são discriminadas de forma estática pelo sistema de tipos, o que gera uma cultura que preza pela pureza das bibliotecas. Garantindo escalabilidade de forma natural.
  • Operações que envolvem I/O são modeladas dentro de um conceito puro (Monad), permitindo composição, backtracking, computação não determinística, etc.
  • Polimorfismo aberto permite uma consolidação de uma única biblioteca e conceito, mesmo que construído de forma incompleta no primeiro release.
  • Avaliação preguiçosa possibilita um melhor gerenciamento do processamento e localidade em ambientes com escalabilidade.
  • Multiparadigmas para concorrência e paralelismo: locks, memória transacional, mensageria, filas, tópicos, paralelismo grátis com balanceamento de carga automático e threads baratas.

É fácil notar que Java já percebeu sua deficiência e busca soluções com as liberações novas. Neste ano será feita a liberação do Java 8 com suporte a mecanismos funcionais (projeto Lambda). A Apache decidiu não esperar pela nova versão e já têm a biblioteca Functor para tentar suprir estas necessidades. Assim também fez o Google com adições funcionais à biblioteca Guava.

Para a JVM já existem algumas linguagens tentando manter a plataforma aderente ao novo modelo de computação, por exemplo: Clojure, Groovy e Scala.

E você, está se preparando? Se você ainda não experimentou nenhuma linguagem funcional, te aconselho a ir logo conhecer o que este paradigma oferece. Seguindo o paradoxo de Blub, o ideal é começar com uma linguagem funcional pura, como Haskell, antes de avaliar linguagens como Scala que são híbridas e oferecem o paradigma tradicional.

3 de abril de 2012

Sobrevivente.run()

Como sobreviver sem repetição de código no reino dos substantivos?

O que fazer quando encontramos código deste tipo:

A.java

Button button = new Button("Limpar");
button.addActionListener(new ActionListener() {
  public void performAction() {
    if (foo) {
      askConfirmation();
      bar.clear();
    }
  }
});

B.java

Button button = new Button("Limpar");
  button.addActionListener(new ActionListener() {
    public void performAction() {
      if (bacon) {
        askConfirmation();
        eggs.clear();
      }
    }
});

C.java

Button button = new Button("Limpar");
button.addActionListener(new ActionListener() {
  public void performAction() {
    if (baz) {
      askConfirmation();
      spam.clear();
    }
  }
});

Meu jeito é criar um verbo disfarçado de substantivo que leva consigo a ação que eu quero:

interface MagicClearWord {
  boolean isDirty();
  void clear();
}
class MagicClearWordExecutor implements ActionListener {
  private final MagicClearWord e;
  MagicClearWordExecutor(MagicClearWord e) {
    this.e = e;
  }
  public void performAction() {
    if (e.isDirty()) {
      askConfirmation();
      e.clear();
    }
  }
}

E depois utilizar como se fosse um "substantivo anônimo" (#nonsense):

A.java

Button button = new Button("Limpar");
button.addActionListener(new MagicClearWordExecutor(
  new MagicClearWord() {
    public boolean isDirty() {
      return foo;
    }
    public void clear() {
      bar.clear();
    }
  })
);

B.java

Button button = new Button("Limpar");
button.addActionListener(new MagicClearWordExecutor(
  new MagicClearWord() {
    public boolean isDirty() {
      return bacon;
    }
    public void clear() {
      eggs.clear();
    }
  })
);

C.java

Button button = new Button("Limpar");
button.addActionListener(new MagicClearWordExecutor(
  new MagicClearWord() {
    public boolean isDirty() {
      return baz;
    }
    public void clear() {
      spam.clear();
    }
  })
);

Ainda assim os substantivos e seus amigos gulosos comem seu tempo e seu espaço em disco com tanta palavra vazia. Parece que estamos presos mesmo.

Oh God, why?

29 de fevereiro de 2012

Levando o conhecimento de Haskell para Java

Resgatando o blog e trocando um pouco o tema para falar sobre programação.

Existem muitos tutoriais sobre orientação a objetos e desenvolvimento orientado por testes (TDD) pela internet. Grande parte se utiliza de um exemplo simples: uma calculadora.

Para desenvolver a calculadora, demonstram o poder do polimorfismo construindo abstrações sobre operações binárias e a utilidade do TDD através de uma programação incremental.

Neste post tentarei extender este exemplo de calculadora, porém focando em outro ponto do problema: a interação com o usuário.

Os exemplos estão na linguagem orientada a objetos mais utilizada do planeta: Java.

  • Programa: Calculadora para terminal.
  • Requisito: Ler cálculos no formato "2 + 2" e informar o usuário sobre o resultado do cálculo: "4".
  • Problema: Como testar a interação com o usuário?

Por ser um programa feito para rodar em terminal, utilizaremos System.in e System.out para interagir com o usuário. Porém, se amarrarmos o algoritmo de cálculo diretamente à estes recursos, ficará muito difícil e não-produtivo criar e manter testes para este sistema.

Algo importante que aprendi com a linguagem Haskell é a de separar o código em áreas puras e impuras. Uma área de código pura é um conjunto de funções que dependem apenas de seus argumentos e só produzem um resultado, i.e. funções que não alteram nenhuma informação e dependem apenas de seus argumentos. A área de código impura é aquela composta pelas demais funções que dependem de I/O, alterações na memória, banco de dados, etc.

A linguagem Haskell faz um trabalho incrível para assegurar que esta distinção seja mantida pelos programadores através do seu sistema de tipos que diferenciam funções puras e funções impuras. Nas linguagens tradicionais, como Java, não existe esta distinção: todo pedaço de código tem a liberdade de ser impuro. Desta forma, os programadores devem sempre prestar atenção para o que cada parte do código está executando.

Para separar a parte "pura" e "impura" da nossa calculadora, precisamos primeiro identificar quais funções pertencem a cada parte.

Toda a parte de código que realiza o cálculo pode se encaixar perfeitamente na área de código pura, afinal são apenas representações para funções matemáticas, que são puras por definição.

Por outro lado, a parte que faz a interação com o usuário é impura, pois altera informações que vão além de seu alcance em termos de código: imprime caracteres no terminal, e recebe informações que vão além de seus argumentos: teclas pressionadas pelo usuário.

A parte impura do código é muito difícil de ser testada e por isto deve ser mantida reduzida e de forma controlada, que faça apenas o necessário para o funcionamento. No caso da calculadora para terminal, a parte impura é apenas interação com o usuário no seguinte formato:

  1. Usuário digita um texto.
  2. Programa responde com outro texto.
  3. Volte para 1.

Esta interação é abstrata o suficiente para ser totalmente separada da parte que realiza o processamento. Por exemplo, o seguinte código encapsularia toda a parte impura da nossa calculadora:

public interface Programa {
  String processar(String entrada);
}

public class InteracaoTerminal {
  public void interagir(Programa programa) {
    Scanner s = new Scanner(System.in);
    while (true) {
      String entrada = s.nextLine();
      String saida = programa.processar(entrada);
      System.out.println(saida);
    }
  }
}

Desta forma a interface Programa é 100% testavel, por exemplo:

assertEquals("4", programa.processar("2 + 2"));
assertEquals("6", programa.processar("2 + 2 * 2"));

Além de testavel, a implementação da nossa calculadora apenas manipulará Strings, sem dependência direta com o System. Isto significa que poderemos aproveitar o código em outros lugares, quem sabe utilizar o mesmo código para duas interfaces diferentes: uma interage com o usuário através de um terminal e outra através de um aplicativo gráfico.

A abstração colocada como exemplo tem uma limitação: cada interação com o usuário é feita através de uma linha de entrada e uma linha de saída. Deixo como exercício aprimorar esta abstração para que seja possível ter mais de uma linha de entrada e mais de uma linha como saída de uma única interação. Se você conhece Haskell, poderá se basear na função interact, e se você não conhece: Aprender Haskell será um grande bem para você!