DEV Community

Rodrigo Sicarelli
Rodrigo Sicarelli

Posted on

Kotlin Koans BR: Tipagem inteligente

🔗 Tarefa

Reescreva o código fornecido usando smart casts e a expressão when do Kotlin.

Java

class Java { public int eval(Expr expr) { if (expr instanceof Num) { return ((Num) expr).getValue(); } if (expr instanceof Sum) { Sum sum = (Sum) expr; return eval(sum.getLeft()) + eval(sum.getRight()); } throw new IllegalArgumentException("Unknown expression"); } } 
Enter fullscreen mode Exit fullscreen mode

C#

public interface Expr { } public class Num : Expr { public int Value { get; set; } } public class Sum : Expr { public Expr Left { get; set; } public Expr Right { get; set; } } public int Eval(Expr expr) { if (expr is Num num) return num.Value; if (expr is Sum sum) return Eval(sum.Left) + Eval(sum.Right); throw new ArgumentException("Unknown expression"); } 
Enter fullscreen mode Exit fullscreen mode

Dart

abstract class Expr {} class Num implements Expr { final int value; Num(this.value); } class Sum implements Expr { final Expr left, right; Sum(this.left, this.right); } int eval(Expr expr) { if (expr is Num) return expr.value; if (expr is Sum) return eval(expr.left) + eval(expr.right); throw ArgumentError('Unknown expression'); } 
Enter fullscreen mode Exit fullscreen mode

Go

package main type Expr interface{} type Num struct { Value int } type Sum struct { Left, Right Expr } func Eval(expr Expr) int { switch e := expr.(type) { case Num: return e.Value case Sum: return Eval(e.Left) + Eval(e.Right) default: panic("Unknown expression") } } 
Enter fullscreen mode Exit fullscreen mode

JavaScript

function eval(expr) { if (expr instanceof Num) { return expr.value; } if (expr instanceof Sum) { return eval(expr.left) + eval(expr.right); } throw new Error("Unknown expression"); } class Num { constructor(value) { this.value = value; } } class Sum { constructor(left, right) { this.left = left; this.right = right; } } 
Enter fullscreen mode Exit fullscreen mode

TypeScript

interface Expr { } class Num implements Expr { constructor(public value: number) { } } class Sum implements Expr { constructor(public left: Expr, public right: Expr) { } } function eval(expr: Expr): number { if (expr instanceof Num) return expr.value; if (expr instanceof Sum) return eval(expr.left) + eval(expr.right); throw new Error("Unknown expression"); } 
Enter fullscreen mode Exit fullscreen mode

PHP

interface Expr {} class Num implements Expr { public $value; function __construct($value) { $this->value = $value; } } class Sum implements Expr { public $left, $right; function __construct($left, $right) { $this->left = $left; $this->right = $right; } } function evalExpr($expr) { if ($expr instanceof Num) return $expr->value; if ($expr instanceof Sum) return evalExpr($expr->left) + evalExpr($expr->right); throw new Exception("Unknown expression"); } 
Enter fullscreen mode Exit fullscreen mode

Python

class Expr: pass class Num(Expr): def __init__(self, value): self.value = value class Sum(Expr): def __init__(self, left, right): self.left = left self.right = right def eval_expr(expr): if isinstance(expr, Num): return expr.value if isinstance(expr, Sum): return eval_expr(expr.left) + eval_expr(expr.right) raise ValueError("Unknown expression") 
Enter fullscreen mode Exit fullscreen mode

Swift

protocol Expr {} class Num: Expr { let value: Int init(_ value: Int) { self.value = value } } class Sum: Expr { let left, right: Expr init(_ left: Expr, _ right: Expr) { self.left = left; self.right = right } } func eval(_ expr: Expr) -> Int { if let num = expr as? Num { return num.value } if let sum = expr as? Sum { return eval(sum.left) + eval(sum.right) } fatalError("Unknown expression") } 
Enter fullscreen mode Exit fullscreen mode

Casos de uso

Em programação, cada tipo de dado é representado e operado diferentemente na memória. O "casting" é uma técnica usada para informar ao compilador que uma variável deve ser tratada como outro tipo. Isso permite realizar operações específicas com essa variável, além de garantir a compatibilidade com outras partes do código.

Em Kotlin, existe um recurso do compilador chamado Smart casts que rastreia verificações de tipos (como com o operador is) e infere automaticamente o seu tipo quando necessário.

Verificação de tipo e inferência

Verificação positiva

Ao verificar uma variável com o operador is, e se a verificação for bem-sucedida, Kotlin reconhece imediatamente o tipo dessa variável dentro do bloco de código:

class Gato(val emojiGato: String = "🐱") class Cachorro(val emojiCachoro: String = "🐶") class Peixe(val emojiPeixe: String = "🐟") class Pássaro(val emojiPassaro: String = "🐦") fun falar(animal: Any): String { return when (animal) { is Gato -> "Miau ${animal.emojiGato}" is Cachorro -> "Au au ${animal.emojiCachoro}" is Peixe -> "Blub blub ${animal.emojiPeixe}" is Pássaro -> "Pi pi ${animal.emojiPassaro}" else -> "Não reconhecemos esse animal." } } fun ondeVive(animal: Any) { if (animal is Gato || animal is Cachorro) { println("Vive em terra.") } else if (animal is Peixe) { println("Vive na água.") } else if (animal is Pássaro) { println("Vive no ar e na terra.") } else { println("Não reconhecemos esse animal.") } } 
Enter fullscreen mode Exit fullscreen mode

Verificação negativa

Usando ! antes do operador is, é possível reagir quando a variável não é do tipo esperado:

class Ave(val canto: String) class Macaco(val grito: String) class Reptil(val som: String = "Ssssss") fun documentarSom(animal: Any) { if (animal !is Ave) return print("O som da ave é: ${animal.canto}") } // Testando a função val tucano = Ave("Pi-pi-piu") documentarSom(tucano) // Saída: "O som da ave é: Pi-pi-piu" 
Enter fullscreen mode Exit fullscreen mode

Limitações com variáveis mutáveis (var)

O compilador pode não realizar um Smart Cast se não puder garantir que o valor da variável não mudou entre o momento da verificação e o momento do uso:

open class Animal class Cachorro() : Animal() { fun alimentar() = Unit } var animal: Animal? = Cachorro() if (animal is Cachorro) { animal = null animal.alimentar() // Erro de compilação: Smart cast para 'Cachorro' é impossível } 
Enter fullscreen mode Exit fullscreen mode

Smart Casts com operadores lógicos

Kotlin vai além e integra a capacidade de "Smart Casts" com operadores lógicos como && e ||. Isso evita a necessidade de conversões explícitas, tornando o código mais limpo e legível.

open class Animal(val nome: String, val energia: Int = 100) class Peixe(nome: String, energia: Int, val habitatPreferido: String) : Animal(nome, energia) { fun explorar() = "está explorando o habitat $habitatPreferido!" } class Passaro(nome: String, energia: Int, val tipoBico: String) : Animal(nome, energia) { fun bicar() = "está usando seu bico $tipoBico para buscar comida!" } fun acaoEspecifica(animal: Animal) { when { animal is Peixe && animal.energia > 50 -> { println("${animal.nome} ${animal.explorar()}") } animal is Passaro && animal.tipoBico == "afiado" -> { println("${animal.nome} ${animal.bicar()}") } else -> { println("${animal.nome} não está realizando uma ação específica no momento.") } } } // Testando a função val tilapia = Peixe("Tilápia", 60, "lago de água doce") val aguia = Passaro("Águia", 80, "afiado") val canario = Passaro("Canário", 50, "pequeno") acaoEspecifica(tilapia) // Saída: "Tilápia está explorando o habitat lago de água doce!" acaoEspecifica(aguia) // Saída: "Águia está usando seu bico afiado para buscar comida!" acaoEspecifica(canario) // Saída: "Canário não está realizando uma ação específica no momento." 
Enter fullscreen mode Exit fullscreen mode

Vantagens

  • Sintaxe limpa e código legível: permite um código mais limpo, direto e legível, evitando repetições de conversões explícitas de tipo.
  • Segurança de tipo: o compilador realiza o Smart Cast apenas quando é seguro, reduzindo a possibilidade de erros de conversão em tempo de execução.
  • Integração com controle de fluxo: dentro de controles condicionais como if, else, when, ou loops como for, while, o Kotlin reconhece e ajusta o tipo da variável de acordo, permitindo o acesso direto a suas propriedades específicas sem necessidade de casting explícito.

Desvantagens

  • Limitações com Variáveis Mutáveis: com variáveis mutáveis, Smart Casts pode não ser garantido pelo compilador, já que o tipo pode ter mudado entre a verificação e o uso.
  • Concorrência: em ambientes com múltiplos threads, o Smart Cast pode apresentar riscos se uma variável for alterada por outro thread após a verificação.
  • Potencial confusão com lógica complexa: em certas lógicas condicionais, o compilador pode não conseguir inferir o tipo, mesmo que pareça claro para o desenvolvedor.

Analogia

Ao ouvir o canto de um pássaro específico na floresta, um ornitólogo pode identificar imediatamente a espécie, mesmo sem vê-la. Esse reconhecimento imediato permite ao especialista saber tudo sobre esse pássaro, desde seus hábitos até seu habitat.

O Smart Cast no Kotlin age de forma semelhante, permitindo utilizar o tipo específico assim que identificado, sem necessidade de verificações adicionais.

Top comments (0)