Simplicidade da abstração: throwable
Tags: typescript, javascript, functional
Published: 2019-11-08 01:21:36

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:

```typescript
const r = throwable(() => 42);
r.return() // 42
```

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:

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

const result = r
    .map(num => num + num)
    .map(num => num * num)
    .return(); // 7056
```

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:

```typescript
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;
}
```

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>`:

```typescript
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;
  }
}
```

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

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

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

  return() {
    return this.x;
  }
}
```

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

```typescript
const throwable = <B>(factory: () => B): Optional<B> => {
  try {
    const result = factory();
    return new Just(result);
  } catch (err) {
    return new Nothing();
  }
};
```

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:

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

console.log(result); // Nothing { x: null }
```

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:

```typescript
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
}
```

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.