Как стать автором
Обновить

Комментарии 14

Отлично написано. Особенно концовка:


Интервьювер: Ну так, и что? Мы вот запускаем, ничего не печатается по заданию
Соискатель: Да ладно, я вам весь инструментарий дал, решение дал, вы просто вывести куда-нибудь не можете?


Замечательный пятничный пост (хотя и в субботу)

Вывод: абстракции позволяют очень долго ходить вокруг да около решения, и пока жители Виллабаджо…
Вот чего не хватило, честно говоря, так это понимания, сколько же всего кода в итоге получилось?

Смотря как считать, все функции кроме собственно fizzBuzz полезны в широком контексте и в проекте были бы общим кодом. Так что самого решения — одна функция в три строки :-)

Так а всего — примерно 20-30?
В принципе, остаток от деления не обязан быть того же типа, что и делимое. Тогда, соответственно, и zero для Т не требуется?

Да, остаток от деления определяется строго говоря делителем, так что нужен zero для типа делителя, да и отрицательные числа из Integral тут не нужны, но это стандартный тайпкласс, проще использовать его, чем писать свой.

возможно глупый вопрос, а зачем писать обобщенный isdivisibleBy, если в итоге для вычисления используется стандартный оператор %?

На самом деле нет, в коде:


  def divisibleBy(n: T)(implicit I: Integral[T], ev: Eq[T]): Boolean = {
    import I._
    (value % n) === zero
  }

Оператор % это синтаксис для I.rem, который импортируется из I, так что это будет работать для любого типа, не только стандартных числовых.

Я бы не стал так делать, очень много букв, очень хрупкий код. Вот мой вариант, на 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 не слишком сегрегированный, тут как по мне лучше стандартное чем небольшое. Код же вполне точно описывает требования, странно называть часть требований "особенностью" — все требования особенные.


Если под вашу задачу подходят существующие абстракции — пожалуйста используйте их, в этом и есть смысл сегрегации — небольшие абстракции лучше используются совместно с другими, а значит повторно. Ваш тайпкласс меньше, но не подходит под повторное использование (кстати он нарушает тот же принцип, поскольку его можно разделить на две ортогональных функциональности).

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории