Fiddling around with some obvious simplification, making use more concise, default int support, etc.

This commit is contained in:
Pat Garrity 2026-06-10 22:41:46 -05:00
parent 439c28c1c7
commit 5727a52415
Signed by: pfm
GPG key ID: 0DC16BCA24B270C4
6 changed files with 94 additions and 48 deletions

View file

@ -1,18 +1,50 @@
package gs.fizzbuzz package gs.fizzbuzz
import cats.Applicative import cats.Applicative
import cats.Monoid
trait Condition[F[_], Input, Output]: trait Condition[F[_], Input]:
def matches(input: Input): F[Boolean] def matches(input: Input): F[Boolean]
def output: Output
end Condition end Condition
object 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] = override def matches(input: Input): F[Boolean] =
Applicative[F].pure(false) Applicative[F].pure(false)
override def output: Nothing =
throw new IllegalStateException("No-op condition cannot produce output.")
end Condition end Condition

View file

@ -3,11 +3,10 @@ package gs.fizzbuzz
import cats.Applicative import cats.Applicative
import cats.Monoid import cats.Monoid
final class DefaultCondition[F[_]: Applicative, Input, Out]( final class DefaultCondition[F[_]: Applicative, Input](
val comparison: Input, val comparison: Input,
val output: Out,
val operator: Monoid[Input] 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] = override def matches(input: Input): F[Boolean] =
Applicative[F].pure( Applicative[F].pure(

View file

@ -1,26 +1,24 @@
package gs.fizzbuzz package gs.fizzbuzz
import cats.syntax.all.* import cats.syntax.all.*
import fs2.Pipe
import cats.FlatMap import cats.FlatMap
class DefaultFizzBuzz[F[_]: FlatMap, Input, Out]( class DefaultFizzBuzz[F[_]: FlatMap, Input](
val fizzCondition: Condition[F, Input, Out], val fizzCondition: Condition[F, Input],
val buzzCondition: Condition[F, Input, Out] val buzzCondition: Condition[F, Input]
) extends FizzBuzz[F, Input, Out]: ) extends FizzBuzz[F, Input]:
def evaluate(input: Input): F[Result[Out]] = def evaluate(input: Input): F[Result] =
for for
fizz <- fizzCondition.matches(input) fizz <- fizzCondition.matches(input)
buzz <- buzzCondition.matches(input) buzz <- buzzCondition.matches(input)
yield yield
if fizz && buzz then if fizz && buzz then
Result.FizzBuzz(fizzCondition.output, buzzCondition.output) Result.FizzBuzz
else if fizz then else if fizz then
Result.Fizz(fizzCondition.output) Result.Fizz
else if buzz then else if buzz then
Result.Buzz(buzzCondition.output) Result.Buzz
else else
Result.Unmatched Result.Unmatched
def pipe(): Pipe[F, Input, Result[Out]] = in => in.evalMap(evaluate)
end DefaultFizzBuzz end DefaultFizzBuzz

View file

@ -1,8 +1,43 @@
package gs.fizzbuzz package gs.fizzbuzz
import fs2.Pipe 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 end FizzBuzz

View file

@ -1,32 +1,15 @@
package gs.fizzbuzz package gs.fizzbuzz
import cats.Monoid
import cats.effect.IO import cats.effect.IO
import cats.effect.IOApp import cats.effect.IOApp
import cats.effect.ExitCode import cats.effect.ExitCode
object Main extends IOApp: object Main extends IOApp:
override def run(args: List[String]): IO[ExitCode] = override def run(args: List[String]): IO[ExitCode] =
val op: Monoid[Long] = new Monoid[Long] { val fb = FizzBuzz.forLong[IO](
override def empty: Long = 0L fizz = 3,
override def combine(x: Long, y: Long): Long = x % y buzz = 5,
} f = (x, y) => 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
) )
for for

View file

@ -1,13 +1,12 @@
package gs.fizzbuzz package gs.fizzbuzz
sealed trait Result[+Out] sealed trait Result
object Result: object Result:
case class Fizz[Out](fizz: Out) extends Result[Out] case object Fizz extends Result
case class Buzz[Out](buzz: Out) extends Result[Out] case object Buzz extends Result
case class FizzBuzz[Out](fizz: Out, buzz: Out) extends Result[Out] case object FizzBuzz extends Result
case object Unmatched extends Result[Nothing]: case object Unmatched extends Result
override def toString(): String = "unmatched"
end Result end Result