- PVSM.RU - https://www.pvsm.ru -
FizzBuzz это известная задачка, шутливо или не очень задаваемая на собеседованиях, существует множество вариантов реализации даже для такой простой игры. Существует даже шедевры вроде FizzBuzzEnterpriseEdition [1].
Предлагаю вашему вниманию еще один вариант, не совсем пятничный, а скорее субботний: FizzBuzz на Scala, functional style.
Для чисел от 1 до 100 нужно выводить на экран
Программист должен не столько решать задачу, сколько создавать инструмент для ее решения
def divisibleBy(n: Int, d: Int): Boolean = n % d == 0
divisibleBy(10, 5) // => true
Нет, это нас не устроит — ведь делимость это свойство не только чисел типа Int
, опишем делимость в общем виде, а за одно сделаем ее инфиксным оператором (Тут и далее используются некоторые возможности библиотеки cats [2]):
import cats.implicits._
import cats.Eq
implicit class DivisionSyntax[T](val value: T) extends AnyVal {
def divisibleBy(n: T)(implicit I: Integral[T], ev: Eq[T]): Boolean = {
import I._
(value % n) === zero
}
def divisibleByInt(n: Int)(implicit I: Integral[T], ev: Eq[T]): Boolean =
divisibleBy(I.fromInt(n))
}
10 divisibleBy 5 // => true
BigInt(10) divisibleBy BigInt(3) // => false
BigInt(10) divisibleByInt 3 // => false
Тут используются:
Integral
" требующий от типа "T
" возможности вычислять остаток от деления и иметь значение "zero
"Eq
" требующий от типа "T
" возможности сравнивать его элементы (оператор "===
" это его синтаксис)T
" с помощью extension methods & value classes [4], которое не имеет рантайм-оверхеда (ждем dotty, который принесет нам нормальный синтаксис экстеншен методов)Строго говоря метод divisibleByInt
не совсем тут нужен, но он пригодится нам позже, если мы захотим использовать литералы целочисленного типа 3 и 5.
Отлично! Перейдем к вычислению того, что нужно вывести на экран, напомню, что это может быть "Fizz", "Buzz", "FizzBuzz" либо само число. Тут есть общий паттерн — некоторое значение участвует в результате, только если выполняется определенное условие. Для этого подойдет Option
, который будет определять используется значение или нет:
def useIf[T](value: T, condition: Boolean) = if (condition) Some(value) else None
Как и в случае с "divisibleBy(10, 5)
" и "10 divisibleBy 5
" задача решается, но как-то некрасиво. Мы ведь хотим не только решить задачу, но и создать инструмент для ее решения, DSL! По-сути, большая часть работы программиста и есть создание DSL разного рода, когда мы отделяем "как сделать" от "что сделать", "10 % 5 == 0
" от "10 divisibleBy 5
".
implicit class WhenSyntax[T](val value: T) extends AnyVal {
def when(condition: Boolean): Option[T] = if (condition) Some(value) else None
}
"Fizz" when (6 divisibleBy 3) // => Some("Fizz")
"Buzz" when (6 divisibleBy 5) // => None
Осталось собрать все вместе! Мы могли бы использовать orElse
и получили бы 3 правильных ответа из 4, но когда мы должны вывести "FizzBuzz" это не сработает, нам нужно получить Some("Fizz") ? Some("Buzz") => Some("FizzBuzz")
. Просто строки можно складывать, но как сложить Option[String]
? Тут на помощь нам приходят монады моноиды [5], cats предоставляет нам все нужные инстансы и даже удобный синтаксис:
def fizzBuzz[T: Integral: Eq: Show](number: T): String =
("Fizz" when (number divisibleByInt 3)) |+|
("Buzz" when (number divisibleByInt 5)) getOrElse
number.show
Тут type class Show
дает типу T
возможность превращения в строку, |+|
синтаксис моноида для сложения и getOrElse
задает значение по-умолчанию. Все в общем виде и для любых типов, мы могли бы и от строк "Fizz" & "Buzz" абстрагироваться, но это лишнее на мой взгляд.
Все, что нам осталось сделать это (1 to 100) map fizzBuzz[Int]
и куда-нибудь вывести результат. Но это уже совсем другая история...
Автор: Alex
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/354006
Ссылки в тексте:
[1] FizzBuzzEnterpriseEdition: https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition
[2] cats: https://typelevel.org/cats/
[3] type class: https://scalac.io/typeclasses-in-scala/
[4] extension methods & value classes: https://docs.scala-lang.org/overviews/core/value-classes.html
[5] моноиды: https://typelevel.org/cats/typeclasses/monoid.html
[6] Источник: https://habr.com/ru/post/506570/?utm_source=habrahabr&utm_medium=rss&utm_campaign=506570
Нажмите здесь для печати.