Home

Awesome

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.

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.