Pull to refresh

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


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

Sign up to leave a comment.

Articles