DEV Community 👩‍💻👨‍💻

Gustavo Santos
Gustavo Santos

Posted on • Updated on

Simplicidade da abstração: throwable

Imagine que você possui o caldeirão do inferno em suas mãos e precisa contê-lo dentro de uma caixa. Como você coloca o inferno dentro de uma caixa?

A abstração pode nos ajudar como desenvolvedores a resolver vários problemas de formas diferentes. Por esse e outros motivos Haskell é uma linguagens considerada tão difícil de ser aprendida, a abstração nos força a exercitar músculos cerebrais que não são trabalhados diariamente pela maioria de nós desenvolvedores.

Neste texto vou usar uma abstração simples, porém poderosa, e um pouquinho de TypeScript para tentar ilustrar que o nosso código de todo dia pode ser melhorado de algumas formas a fim de ser mais seguro.


Throwable é uma função simples, ela recebe uma função que pode disparar uma exceção e retorna uma caixa mágica. Esta caixa possui dois métodos implementados, map que recebe uma função, aplica esta função ao retorno do callback que throwable recebe e retorna uma outra caixa mágica. Outro método implementado é o return, que apenas retorna o valor que existe dentro da caixa. Considere o exemplo a seguir:

const r = throwable(() => 42);
r.return() // 42
Enter fullscreen mode Exit fullscreen mode

O que está acontecendo aqui? throwable recebe uma "factory function", uma função getter que produz um resultado e, este resultado é guardado dentro da caixa mágica. Quando a segunda linha é executada, o valor 42, que até então estava guardado dentro da caixa, é exposto para o mundo externo.

Hum, você, pessoa pensativa, pode estar se perguntando se é só isso mesmo. Bom, considere esse outro exemplo:

const r = throwable(() => 42);

const result = r
    .map(num => num + num)
    .map(num => num * num)
    .return(); // 7056
Enter fullscreen mode Exit fullscreen mode

O que está acontecendo aí em cima??? Imagine que você jogou o número 42 dentro de uma caixa, depois jogou uma função num => num + num dentro dessa caixa e depois ainda jogou outra função num => num * num nessa mesma caixa e no final, misteriosamente tirou o número 7056 da caixa 😱😱😱😱.

Mas no final tudo faz sentido: imagina que na caixa existia o número 42, então você aplicou a função num => num + num no número 42, ou seja você transformou o número 42 que existia dentro da caixa, no número 84. Depois você aplicou a função num => num * num no número 84, o que produziu o número 7056. Agora faz sentido?

Ok, talvez explicar como isso funciona seja mais educativo, então vamos lá!

Lembra da abstração? Fazer algo desse tipo exige um nível de abstração um pouco maior, não estamos mais lidando com bits e bytes, mas sim com caixas. A base da função throwable é uma classe abstrata chamada Optional<A>, dê uma olhada nessa classe:

abstract class Optional<A> {
  protected x: A;
  constructor(x: A) {
    this.x = x;
  }

  abstract map<B>(f: (x: A) => B): Optional<B>;
  abstract return(): A;
}
Enter fullscreen mode Exit fullscreen mode

Conhecer TypeScript é de grande valia neste caso, mas tentarei explicar o que acontece aqui: Optional é uma classe abstrata que possui dois métodos abstratos, map e return, ambos métodos devem ser implementados por todas as classes que estendam de Optional. Também existe o atributo protegido x, do tipo A, este atributo é acessível somente às classes que estendam de Optional<A>. Insight importante: a nossa caixa mágica na verdade é uma instância da classe Optional<A>.

Ok, mas quem estende desta classe? As classes Just<A> e Nothing<A>.

Olhe a implementação da classe Just<A>:

class Just<A> extends Optional<A> {
  constructor(x: A) {
    super(x);
  }

  map<B>(f: (x: A) => B): Optional<B> {
    // olha o throwable aqui
    return throwable(() => f(this.x));
  }

  return() {
    return this.x;
  }
}
Enter fullscreen mode Exit fullscreen mode

Agora olhe a implementação da classe Nothing<A>:

class Nothing<A> extends Optional<A> {
  constructor() {
    super(null);
  }

  map<B>(): Optional<B> {
    return new Nothing();
  }

  return() {
    return this.x;
  }
}
Enter fullscreen mode Exit fullscreen mode

Está conseguindo grudar uma coisa na outra? Talvez? Dê uma olhada na implementação da função throwable então:

const throwable = <B>(factory: () => B): Optional<B> => {
  try {
    const result = factory();
    return new Just(result);
  } catch (err) {
    return new Nothing();
  }
};
Enter fullscreen mode Exit fullscreen mode

A função throwable não se importa se acontecer algum erro durante a avaliação da função factory. Caso aconteça, apenas será retornado uma instância da classe Nothing. Caso a avaliação de factory produza qualquer resultado sem que uma exceção seja disparada, uma instância da classe Just será retornada. Como tanto Just e Nothing estendem de Optional e Optional exige que os métodos base sejam implementados, há total compatibilidade entre as instâncias das duas classes, sendo possível encadear chamadas a map, mesmo em casos onde ocorra um erro.

Agora você possui o inferno dentro de uma caixa. Dê uma olhada neste exemplo de uso desta abstração:

const result = throwable(() => JSON.parse("{ 32"))
  .map(num => num + num)
  .map(num => num * num);

console.log(result); // Nothing { x: null }
Enter fullscreen mode Exit fullscreen mode

Nenhum erro ocorreu. Você jogou uma operação que pode disparar uma exceção, mas tudo bem, você apenas receberá de volta uma instância da classe Nothing, como nesse próximo exemplo:

const deepAccess = (obj: any) => obj.a.b.c;

const result = throwable(() => deepAccess({ a: 1 }))
  .map(num => num + num)
  .map(num => num * num);

if (result instanceof Just) {
  console.log("sucess: ", result.return());
} else {
  console.log("fail"); // fail
}
Enter fullscreen mode Exit fullscreen mode

Mesmo caso, acessar a propriedade a.b.c do objeto { a: 1 } causa um erro de runtime, que será abstraído pela função throwable.


No final das contas, throwable apenas oferece uma camada de abstração para operações síncronas que podem causar erros em tempo de execução do JavaScript. Oferecer essa mesma abstração para funções assíncronas é tema de um próximo post.

Top comments (0)

This post blew up on DEV in 2020:

js visualized

🚀⚙️ JavaScript Visualized: the JavaScript Engine

As JavaScript devs, we usually don't have to deal with compilers ourselves. However, it's definitely good to know the basics of the JavaScript engine and see how it handles our human-friendly JS code, and turns it into something machines understand! 🥳

Happy coding!