Skip to content

💎 Haskell version of the Shopping Cart application developed in the book "Practical FP in Scala: A hands-on approach"

License

Notifications You must be signed in to change notification settings

gvolpe/shopping-cart-haskell

Repository files navigation

shopping-cart-haskell

CI Status

Haskell version of the Shopping Cart developed in the book Practical FP in Scala.

How to run

Within a Nix shell (run nix-shell - recommended), follow the commands below.

Run web application

cabal new-run shopping-cart

Run tests

cabal new-run shopping-cart-tests

Comparison with the Scala application

The original version of the Shopping Cart has been written in Scala. The Haskell application's design is quite similar.

  • Services are represented using polymorphic records of functions.
  • The Newtypes / Refined duo is used for strongly-typed data.
  • Retry is used for retrying computations compositionally.
  • Servant is used as the default HTTP server.
  • Wreq is used as the default HTTP client.
  • Hedis is used as the default Redis client.
  • PostgreSQL Simple, PostgreSQL Resilient, and Raw Strings QQ are used to handle PostgreSQL stuff.

A polymorphic record of functions looks as follows:

data Brands m = Brands
  { findAll :: m [Brand]
  , create :: BrandName -> m ()
  }

Whereas in Scala, we represent it using traits (although case class / classes would work too):

trait Brands[F[_]] {
  def findAll: F[List[Brand]]
  def create(name: BrandName): F[Unit]
}

We pass them along as explicit dependencies, though, so we don't treat them as typeclasses. In Scala, we use the same encoding for typeclasses (and then pass them implicitly) but in Haskell the encoding differs:

class Brands m where
  findAll :: m [Brand]
  create :: BrandName -> m ()

Typeclasses were used to encode effects such as Background, Logger and Retry:

class Background m where
  schedule :: m a -> Minutes -> m ()

instance Background IO where
  schedule fa mins = void $ async (threadDelay (microseconds mins) >> fa)
    where microseconds Mins {..} = 60000000 * unrefine unMins

In Scala, this is how it is encoded:

trait Background[F[_]] {
  def schedule[A](fa: F[A], duration: FiniteDuration): F[Unit]
}

object Background {
  def apply[F[_]: Background]: Background[F] = implicitly

  implicit def bgInstance[F[_]](implicit S: Supervisor[F], T: Temporal[F]): Background[F] =
    new Background[F] {
      def schedule[A](fa: F[A], duration: FiniteDuration): F[Unit] =
        S.supervise(T.sleep(duration) *> fa).void
    }
}

Having an implicit implementation is how we define coherent instances, though, they can be easily overridden (but this is also possible in Haskell using the right extensions).

Missing features

There are some features I don't plan to implement due to lack of time and motivation.

  • JWT Authentication: if you want to get it done, here's some nice documentation on how to get started.
  • Configuration: I recommend using Dhall. You can have a look at how it's done here.
  • Tests: the machinery is in place but it's a ton of work to write tests. I'll leave that for another day :)

About

💎 Haskell version of the Shopping Cart application developed in the book "Practical FP in Scala: A hands-on approach"

Topics

Resources

License

Stars

Watchers

Forks