Compare commits

..

2 commits

6 changed files with 126 additions and 65 deletions

View file

@ -1,18 +1,60 @@
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](
override def matches(input: Input): F[Boolean] = 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) Applicative[F].pure(false)
override def output: Nothing =
throw new IllegalStateException("No-op condition cannot produce output.")
end Condition end Condition

View file

@ -3,13 +3,15 @@ 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(
operator.combine(input, comparison) == operator.empty operator.combine(input, comparison) == operator.empty
) )

View file

@ -1,26 +1,21 @@
package gs.fizzbuzz package gs.fizzbuzz
import cats.syntax.all.*
import fs2.Pipe
import cats.FlatMap import cats.FlatMap
import cats.syntax.all.*
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
Result.FizzBuzz(fizzCondition.output, buzzCondition.output) else if fizz then Result.Fizz
else if fizz then else if buzz then Result.Buzz
Result.Fizz(fizzCondition.output) else Result.Unmatched
else if buzz then
Result.Buzz(buzzCondition.output)
else
Result.Unmatched
def pipe(): Pipe[F, Input, Result[Out]] = in => in.evalMap(evaluate)
end DefaultFizzBuzz end DefaultFizzBuzz

View file

@ -1,8 +1,45 @@
package gs.fizzbuzz package gs.fizzbuzz
import cats.Applicative
import cats.FlatMap
import fs2.Pipe import fs2.Pipe
trait FizzBuzz[F[_], Input, Out]: trait FizzBuzz[F[_], Input]:
def evaluate(input: Input): F[Result[Out]] def evaluate(input: Input): F[Result]
def pipe(): Pipe[F, Input, Result[Out]] 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)
end FizzBuzz end FizzBuzz

View file

@ -1,42 +1,28 @@
package gs.fizzbuzz package gs.fizzbuzz
import cats.Monoid import cats.effect.ExitCode
import cats.effect.IO import cats.effect.IO
import cats.effect.IOApp import cats.effect.IOApp
import cats.effect.ExitCode
object Main extends IOApp: 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( override def run(args: List[String]): IO[ExitCode] =
comparison = 3L, val fb = FizzBuzz.forLong[IO](
output = "Fizz", fizz = 3,
operator = op buzz = 5,
f = (
x,
y
) => x % y
) )
val buzzCond: Condition[IO, Long, String] = new DefaultCondition( for
comparison = 5L,
output = "Buzz",
operator = op
)
val fb: FizzBuzz[IO, Long, String] = new DefaultFizzBuzz[IO, Long, String](
fizzCondition = fizzCond,
buzzCondition = buzzCond
)
for
ex1 <- fb.evaluate(15) ex1 <- fb.evaluate(15)
ex2 <- fb.evaluate(9) ex2 <- fb.evaluate(9)
ex3 <- fb.evaluate(20) ex3 <- fb.evaluate(20)
ex4 <- fb.evaluate(13) ex4 <- fb.evaluate(13)
_ <- IO.println(ex1) _ <- IO.println(ex1)
_ <- IO.println(ex2) _ <- IO.println(ex2)
_ <- IO.println(ex3) _ <- IO.println(ex3)
_ <- IO.println(ex4) _ <- IO.println(ex4)
yield yield ExitCode.Success
ExitCode.Success

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