Comments 14
Отлично написано. Особенно концовка:
Интервьювер: Ну так, и что? Мы вот запускаем, ничего не печатается по заданию
Соискатель: Да ладно, я вам весь инструментарий дал, решение дал, вы просто вывести куда-нибудь не можете?
Замечательный пятничный пост (хотя и в субботу)
возможно глупый вопрос, а зачем писать обобщенный isdivisibleBy, если в итоге для вычисления используется стандартный оператор %?
Я бы не стал так делать, очень много букв, очень хрупкий код. Вот мой вариант, на Tagless Final:
import simulacrum._
import scala.language.implicitConversions
// define operations on A required by FizzBuzz business logic
@typeclass
trait FizzBuzzNumber[A] {
// return true if a is divisible by v
def divisibleBy(a: A, v: Int): Boolean
// format A according to FizzBuzz requirements
def show(a: A): String
}
// import simulacrum-generated ops on A
import FizzBuzzNumber.ops._
// totally abstract business logic
def row[A: FizzBuzzNumber](a: A): String = {
val by3 = a.divisibleBy(3)
val by5 = a.divisibleBy(5)
if (by3 && by5) "FizzBuzz"
else if (by3) "Fizz"
else if (by5) "Buzz"
else a.show
}
//
// implement FizzBuzz on ints
//
implicit val intInstance: FizzBuzzNumber[Int] = new FizzBuzzNumber[Int] {
def divisibleBy(a: Int, v: Int): Boolean = a % v == 0
def show(a: Int): String = a.toString
}
// lazy rows, to do not allocate memory for all 100 rows at once
val rows: Iterable[String] = (1 to 100).view.map(v => row(v))
rows.foreach(println)
Запускаемый вариант: https://scastie.scala-lang.org/PlKt79BOQ6afkgHveSjyIA
Я бы не назвал это tagless final, просто тайпкласс под специфичную проблему, что в целом не очень хорошо, так как такой тайпкласс не будет повторно использован.
В вашем коде (как и в моем) нет повторно используемых компонентов, помимо, возможно, функции fizzBuz/row
Комбинаторы и синтаксический сахар не в счет, т.к. затраты на использование готового компонента (время на его изучение, запоминание и привыкание к нему) должны многократно превышать затраты на написание своего костыля (который не нужно изучать совсем)
Ваш вариант fizzBuz нарушает I в SOLID — передаваемые тайпклассы избыточны и если клиент захочет использовать эту функцию со сторонним типом, у него будут проблемы. С другой стороны, эти тайпклассы не делают бизнес-логику более понятной — идеально написанный код по требованиям должен совпадать с требованиями построчно, вы же используете "особенность" — что FizzBuzz состоит из Fizz и Buzz
Не соглашусь с возражением о повторном использовании — эти инструменты достаточно просты, настолько небольшой код не стоит подключения как библиотеки, но использования в рамках проекта как утилиты — вполне.
Что касается SOLID, я пожалуй даже соглашусь, увы это вызвано желанием использовать стандартный тайпкласс, который в Scala не слишком сегрегированный, тут как по мне лучше стандартное чем небольшое. Код же вполне точно описывает требования, странно называть часть требований "особенностью" — все требования особенные.
Если под вашу задачу подходят существующие абстракции — пожалуйста используйте их, в этом и есть смысл сегрегации — небольшие абстракции лучше используются совместно с другими, а значит повторно. Ваш тайпкласс меньше, но не подходит под повторное использование (кстати он нарушает тот же принцип, поскольку его можно разделить на две ортогональных функциональности).
Functional FizzBuzz на Scala