{"id":13681604,"url":"https://github.com/gvolpe/shopping-cart-haskell","last_synced_at":"2025-03-20T23:30:55.797Z","repository":{"id":48529797,"uuid":"245466814","full_name":"gvolpe/shopping-cart-haskell","owner":"gvolpe","description":":gem: Haskell version of the Shopping Cart application developed in the book \"Practical FP in Scala: A hands-on approach\"","archived":false,"fork":false,"pushed_at":"2021-07-24T12:58:05.000Z","size":163,"stargazers_count":64,"open_issues_count":0,"forks_count":6,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-17T18:13:04.834Z","etag":null,"topics":["functional-programming","http","newtypes","postgresql","refined","servant"],"latest_commit_sha":null,"homepage":"https://leanpub.com/pfp-scala","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gvolpe.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-03-06T16:24:43.000Z","updated_at":"2024-11-02T20:24:46.000Z","dependencies_parsed_at":"2022-08-29T00:40:36.525Z","dependency_job_id":null,"html_url":"https://github.com/gvolpe/shopping-cart-haskell","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gvolpe%2Fshopping-cart-haskell","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gvolpe%2Fshopping-cart-haskell/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gvolpe%2Fshopping-cart-haskell/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gvolpe%2Fshopping-cart-haskell/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gvolpe","download_url":"https://codeload.github.com/gvolpe/shopping-cart-haskell/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244710530,"owners_count":20497254,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-programming","http","newtypes","postgresql","refined","servant"],"created_at":"2024-08-02T13:01:32.922Z","updated_at":"2025-03-20T23:30:55.520Z","avatar_url":"https://github.com/gvolpe.png","language":"Haskell","funding_links":[],"categories":["Haskell"],"sub_categories":[],"readme":"shopping-cart-haskell\n=====================\n\n[![CI Status](https://github.com/gvolpe/shopping-cart-haskell/workflows/Haskell%20CI/badge.svg)](https://github.com/gvolpe/shopping-cart-haskell/actions)\n\nHaskell version of the Shopping Cart developed in the book [Practical FP in Scala](https://leanpub.com/pfp-scala).\n\n### How to run\n\nWithin a [Nix](https://nixos.org/) shell (run `nix-shell` - recommended), follow the commands below.\n\n#### Run web application\n\n```\ncabal new-run shopping-cart\n```\n\n#### Run tests\n\n```\ncabal new-run shopping-cart-tests\n```\n\n## Comparison with the Scala application\n\nThe [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.\n\n- Services are represented using polymorphic records of functions.\n- The Newtypes / [Refined](https://hackage.haskell.org/package/refined) duo is used for strongly-typed data.\n- [Retry](https://hackage.haskell.org/package/retry) is used for retrying computations compositionally.\n- [Servant](https://hackage.haskell.org/package/servant) is used as the default HTTP server.\n- [Wreq](https://hackage.haskell.org/package/wreq) is used as the default HTTP client.\n- [Hedis](https://hackage.haskell.org/package/hedis) is used as the default Redis client.\n- [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.\n\nA polymorphic record of functions looks as follows:\n\n```haskell\ndata Brands m = Brands\n  { findAll :: m [Brand]\n  , create :: BrandName -\u003e m ()\n  }\n```\n\nWhereas in Scala, we represent it using `trait`s (although `case class` / `class`es would work too):\n\n```scala\ntrait Brands[F[_]] {\n  def findAll: F[List[Brand]]\n  def create(name: BrandName): F[Unit]\n}\n```\n\nWe 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:\n\n```haskell\nclass Brands m where\n  findAll :: m [Brand]\n  create :: BrandName -\u003e m ()\n```\n\nTypeclasses were used to encode effects such as `Background`, `Logger` and `Retry`:\n\n```haskell\nclass Background m where\n  schedule :: m a -\u003e Minutes -\u003e m ()\n\ninstance Background IO where\n  schedule fa mins = void $ async (threadDelay (microseconds mins) \u003e\u003e fa)\n    where microseconds Mins {..} = 60000000 * unrefine unMins\n```\n\nIn Scala, this is how it is encoded:\n\n```scala\ntrait Background[F[_]] {\n  def schedule[A](fa: F[A], duration: FiniteDuration): F[Unit]\n}\n\nobject Background {\n  def apply[F[_]: Background]: Background[F] = implicitly\n\n  implicit def bgInstance[F[_]](implicit S: Supervisor[F], T: Temporal[F]): Background[F] =\n    new Background[F] {\n      def schedule[A](fa: F[A], duration: FiniteDuration): F[Unit] =\n        S.supervise(T.sleep(duration) *\u003e fa).void\n    }\n}\n```\n\nHaving 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).\n\n### Missing features\n\nThere are some features I don't plan to implement due to lack of time and motivation.\n\n- 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.\n- 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).\n- Tests: the machinery is in place but it's a ton of work to write tests. I'll leave that for another day :)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgvolpe%2Fshopping-cart-haskell","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgvolpe%2Fshopping-cart-haskell","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgvolpe%2Fshopping-cart-haskell/lists"}