From 5727a52415c76da01d429652378cce671b987e93 Mon Sep 17 00:00:00 2001 From: Pat Garrity Date: Wed, 10 Jun 2026 22:41:46 -0500 Subject: [PATCH] Fiddling around with some obvious simplification, making use more concise, default int support, etc. --- src/main/scala/gs/fizzbuzz/Condition.scala | 42 ++++++++++++++++--- .../scala/gs/fizzbuzz/DefaultCondition.scala | 5 +-- .../scala/gs/fizzbuzz/DefaultFizzBuzz.scala | 18 ++++---- src/main/scala/gs/fizzbuzz/FizzBuzz.scala | 41 ++++++++++++++++-- src/main/scala/gs/fizzbuzz/Main.scala | 25 ++--------- src/main/scala/gs/fizzbuzz/Result.scala | 11 +++-- 6 files changed, 94 insertions(+), 48 deletions(-) diff --git a/src/main/scala/gs/fizzbuzz/Condition.scala b/src/main/scala/gs/fizzbuzz/Condition.scala index 312aade..966d50f 100644 --- a/src/main/scala/gs/fizzbuzz/Condition.scala +++ b/src/main/scala/gs/fizzbuzz/Condition.scala @@ -1,18 +1,50 @@ package gs.fizzbuzz import cats.Applicative +import cats.Monoid -trait Condition[F[_], Input, Output]: +trait Condition[F[_], Input]: def matches(input: Input): F[Boolean] - def output: Output end Condition object Condition: - final class Noop[F[_]: Applicative, Input] extends Condition[F, Input, Nothing]: + def apply[F[_]: Applicative, Input]( + comparison: Input, + operator: Monoid[Input] + )(using CanEqual[Input, Input]): Condition[F, Input] = + new DefaultCondition[F, Input](comparison, operator) + + def forInt[F[_]: Applicative]( + comparison: Int, + operator: (Int, Int) => Int + ): Condition[F, Int] = + new DefaultCondition[F, Int](comparison, makeIntMonoid(operator)) + + def forLong[F[_]: Applicative]( + comparison: Long, + operator: (Long, Long) => Long + ): Condition[F, Long] = + new DefaultCondition[F, Long](comparison, makeLongMonoid(operator)) + + private def makeIntMonoid(op: (Int, Int) => Int): Monoid[Int] = + new Monoid[Int] { + override def empty: Int = 0 + + override def combine(x: Int, y: Int): Int = op(x, y) + } + + private def makeLongMonoid(op: (Long, Long) => Long): Monoid[Long] = + new Monoid[Long] { + override def empty: Long = 0 + + override def combine(x: Long, y: Long): Long = op(x, y) + } + + def noop[F[_]: Applicative, Input]: Condition[F, Input] = new Noop[F, Input] + + final class Noop[F[_]: Applicative, Input] extends Condition[F, Input]: override def matches(input: Input): F[Boolean] = Applicative[F].pure(false) - override def output: Nothing = - throw new IllegalStateException("No-op condition cannot produce output.") end Condition diff --git a/src/main/scala/gs/fizzbuzz/DefaultCondition.scala b/src/main/scala/gs/fizzbuzz/DefaultCondition.scala index ee50a14..ae1307f 100644 --- a/src/main/scala/gs/fizzbuzz/DefaultCondition.scala +++ b/src/main/scala/gs/fizzbuzz/DefaultCondition.scala @@ -3,11 +3,10 @@ package gs.fizzbuzz import cats.Applicative import cats.Monoid -final class DefaultCondition[F[_]: Applicative, Input, Out]( +final class DefaultCondition[F[_]: Applicative, Input]( val comparison: Input, - val output: Out, val operator: Monoid[Input] -)(using CanEqual[Input, Input]) extends Condition[F, Input, Out]: +)(using CanEqual[Input, Input]) extends Condition[F, Input]: override def matches(input: Input): F[Boolean] = Applicative[F].pure( diff --git a/src/main/scala/gs/fizzbuzz/DefaultFizzBuzz.scala b/src/main/scala/gs/fizzbuzz/DefaultFizzBuzz.scala index 6433728..6ae24cd 100644 --- a/src/main/scala/gs/fizzbuzz/DefaultFizzBuzz.scala +++ b/src/main/scala/gs/fizzbuzz/DefaultFizzBuzz.scala @@ -1,26 +1,24 @@ package gs.fizzbuzz import cats.syntax.all.* -import fs2.Pipe import cats.FlatMap -class DefaultFizzBuzz[F[_]: FlatMap, Input, Out]( - val fizzCondition: Condition[F, Input, Out], - val buzzCondition: Condition[F, Input, Out] -) extends FizzBuzz[F, Input, Out]: - def evaluate(input: Input): F[Result[Out]] = +class DefaultFizzBuzz[F[_]: FlatMap, Input]( + val fizzCondition: Condition[F, Input], + val buzzCondition: Condition[F, Input] +) extends FizzBuzz[F, Input]: + def evaluate(input: Input): F[Result] = for fizz <- fizzCondition.matches(input) buzz <- buzzCondition.matches(input) yield if fizz && buzz then - Result.FizzBuzz(fizzCondition.output, buzzCondition.output) + Result.FizzBuzz else if fizz then - Result.Fizz(fizzCondition.output) + Result.Fizz else if buzz then - Result.Buzz(buzzCondition.output) + Result.Buzz else Result.Unmatched - def pipe(): Pipe[F, Input, Result[Out]] = in => in.evalMap(evaluate) end DefaultFizzBuzz diff --git a/src/main/scala/gs/fizzbuzz/FizzBuzz.scala b/src/main/scala/gs/fizzbuzz/FizzBuzz.scala index 77333ee..1a52f59 100644 --- a/src/main/scala/gs/fizzbuzz/FizzBuzz.scala +++ b/src/main/scala/gs/fizzbuzz/FizzBuzz.scala @@ -1,8 +1,43 @@ package gs.fizzbuzz import fs2.Pipe +import cats.Applicative +import cats.FlatMap + +trait FizzBuzz[F[_], Input]: + def evaluate(input: Input): F[Result] + def pipe(): Pipe[F, Input, Result] = in => in.evalMap(evaluate) +end FizzBuzz + +object FizzBuzz: + + def apply[F[_]: FlatMap, Input]( + fizzCondition: Condition[F, Input], + buzzCondition: Condition[F, Input] + ): FizzBuzz[F, Input] = new DefaultFizzBuzz[F, Input](fizzCondition, buzzCondition) + + def forInt[F[_]: FlatMap: Applicative]( + fizz: Int, + buzz: Int, + f: (Int, Int) => Int + ): FizzBuzz[F, Int] = new DefaultFizzBuzz[F, Int]( + fizzCondition = Condition.forInt[F](fizz, f), + buzzCondition = Condition.forInt[F](buzz, f) + ) + + def forLong[F[_]: FlatMap: Applicative]( + fizz: Long, + buzz: Long, + f: (Long, Long) => Long + ): FizzBuzz[F, Long] = new DefaultFizzBuzz[F, Long]( + fizzCondition = Condition.forLong[F](fizz, f), + buzzCondition = Condition.forLong[F](buzz, f) + ) + + def noop[F[_]: Applicative, Input]: FizzBuzz[F, Input] = new Noop[F, Input] + + final class Noop[F[_]: Applicative, Input] extends FizzBuzz[F, Input]: + override def evaluate(input: Input): F[Result] = + Applicative[F].pure(Result.Unmatched) -trait FizzBuzz[F[_], Input, Out]: - def evaluate(input: Input): F[Result[Out]] - def pipe(): Pipe[F, Input, Result[Out]] end FizzBuzz diff --git a/src/main/scala/gs/fizzbuzz/Main.scala b/src/main/scala/gs/fizzbuzz/Main.scala index 256dd70..d95ad28 100644 --- a/src/main/scala/gs/fizzbuzz/Main.scala +++ b/src/main/scala/gs/fizzbuzz/Main.scala @@ -1,32 +1,15 @@ package gs.fizzbuzz -import cats.Monoid import cats.effect.IO import cats.effect.IOApp import cats.effect.ExitCode object Main extends IOApp: override def run(args: List[String]): IO[ExitCode] = - val op: Monoid[Long] = new Monoid[Long] { - override def empty: Long = 0L - override def combine(x: Long, y: Long): Long = x % y - } - - val fizzCond: Condition[IO, Long, String] = new DefaultCondition( - comparison = 3L, - output = "Fizz", - operator = op - ) - - val buzzCond: Condition[IO, Long, String] = new DefaultCondition( - comparison = 5L, - output = "Buzz", - operator = op - ) - - val fb: FizzBuzz[IO, Long, String] = new DefaultFizzBuzz[IO, Long, String]( - fizzCondition = fizzCond, - buzzCondition = buzzCond + val fb = FizzBuzz.forLong[IO]( + fizz = 3, + buzz = 5, + f = (x, y) => x % y ) for diff --git a/src/main/scala/gs/fizzbuzz/Result.scala b/src/main/scala/gs/fizzbuzz/Result.scala index e5e5d5a..749b5a8 100644 --- a/src/main/scala/gs/fizzbuzz/Result.scala +++ b/src/main/scala/gs/fizzbuzz/Result.scala @@ -1,13 +1,12 @@ package gs.fizzbuzz -sealed trait Result[+Out] +sealed trait Result object Result: - case class Fizz[Out](fizz: Out) extends Result[Out] - case class Buzz[Out](buzz: Out) extends Result[Out] - case class FizzBuzz[Out](fizz: Out, buzz: Out) extends Result[Out] - case object Unmatched extends Result[Nothing]: - override def toString(): String = "unmatched" + case object Fizz extends Result + case object Buzz extends Result + case object FizzBuzz extends Result + case object Unmatched extends Result end Result