Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/gvolpe/shopping-cart-haskell
:gem: Haskell version of the Shopping Cart application developed in the book "Practical FP in Scala: A hands-on approach"
https://github.com/gvolpe/shopping-cart-haskell
functional-programming http newtypes postgresql refined servant
Last synced: 13 days ago
JSON representation
:gem: Haskell version of the Shopping Cart application developed in the book "Practical FP in Scala: A hands-on approach"
- Host: GitHub
- URL: https://github.com/gvolpe/shopping-cart-haskell
- Owner: gvolpe
- License: apache-2.0
- Created: 2020-03-06T16:24:43.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2021-07-24T12:58:05.000Z (over 3 years ago)
- Last Synced: 2024-10-11T23:45:41.261Z (30 days ago)
- Topics: functional-programming, http, newtypes, postgresql, refined, servant
- Language: Haskell
- Homepage: https://leanpub.com/pfp-scala
- Size: 159 KB
- Stars: 64
- Watchers: 8
- Forks: 6
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
shopping-cart-haskell
=====================[![CI Status](https://github.com/gvolpe/shopping-cart-haskell/workflows/Haskell%20CI/badge.svg)](https://github.com/gvolpe/shopping-cart-haskell/actions)
Haskell version of the Shopping Cart developed in the book [Practical FP in Scala](https://leanpub.com/pfp-scala).
### How to run
Within a [Nix](https://nixos.org/) 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](https://github.com/gvolpe/pfps-shopping-cart) of the Shopping Cart has been written in [Scala](https://www.scala-lang.org/). The Haskell application's design is quite similar.
- Services are represented using polymorphic records of functions.
- The Newtypes / [Refined](https://hackage.haskell.org/package/refined) duo is used for strongly-typed data.
- [Retry](https://hackage.haskell.org/package/retry) is used for retrying computations compositionally.
- [Servant](https://hackage.haskell.org/package/servant) is used as the default HTTP server.
- [Wreq](https://hackage.haskell.org/package/wreq) is used as the default HTTP client.
- [Hedis](https://hackage.haskell.org/package/hedis) is used as the default Redis client.
- [PostgreSQL Simple](https://hackage.haskell.org/package/postgresql-simple), [PostgreSQL Resilient](https://hackage.haskell.org/package/postgresql-resilient), and [Raw Strings QQ](https://hackage.haskell.org/package/raw-strings-qq) are used to handle PostgreSQL stuff.A polymorphic record of functions looks as follows:
```haskell
data Brands m = Brands
{ findAll :: m [Brand]
, create :: BrandName -> m ()
}
```Whereas in Scala, we represent it using `trait`s (although `case class` / `class`es would work too):
```scala
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:
```haskell
class Brands m where
findAll :: m [Brand]
create :: BrandName -> m ()
```Typeclasses were used to encode effects such as `Background`, `Logger` and `Retry`:
```haskell
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:
```scala
trait Background[F[_]] {
def schedule[A](fa: F[A], duration: FiniteDuration): F[Unit]
}object Background {
def apply[F[_]: Background]: Background[F] = implicitlyimplicit 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](https://docs.servant.dev/en/stable/cookbook/jwt-and-basic-auth/JWTAndBasicAuth.html) on how to get started.
- Configuration: I recommend using [Dhall](https://dhall-lang.org/). You can have a look at how it's done [here](https://github.com/gvolpe/musikell).
- Tests: the machinery is in place but it's a ton of work to write tests. I'll leave that for another day :)