vary (empty) → 0.1.0.0
raw patch · 12 files changed
+1951/−0 lines, 12 filesdep +basedep +deepseqdep +doctest-parallelsetup-changed
Dependencies added: base, deepseq, doctest-parallel, hspec, markdown-unlit, should-not-typecheck, vary
Files
- CHANGELOG.md +11/−0
- LICENSE +7/−0
- README.md +341/−0
- Setup.hs +2/−0
- src/Vary.hs +509/−0
- src/Vary/Core.hs +136/−0
- src/Vary/Utils.hs +224/−0
- src/Vary/VEither.hs +272/−0
- test/README.lhs +341/−0
- test/doctest/doctests.hs +10/−0
- test/spec/spec.hs +3/−0
- vary.cabal +95/−0
+ CHANGELOG.md view
@@ -0,0 +1,11 @@+# Changelog for `vary`++All notable changes to this project will be documented in this file.++The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),+and this project adheres to the+[Haskell Package Versioning Policy](https://pvp.haskell.org/).++## Unreleased++## 0.1.0.0 - YYYY-MM-DD
+ LICENSE view
@@ -0,0 +1,7 @@+Copyright © 2024 Marten Wijnja (Qqwy)++Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,341 @@+# Vary: friendly and fast Variant types for Haskell+[](https://hackage.haskell.org/package/vary)+[](https://hackage.haskell.org/package/vary/docs/Vary.html)+[](https://github.com/Qqwy/haskell-vary/actions/workflows/test.yaml)+++[API Documentation](https://hackage.haskell.org/package/vary/docs/Vary.html)+++Just like tuples are a version of a user-defined product type (only without the field names), a Variant is a version of a user-defined sum type (but without the field names).++Variant types are the generalization of `Either`. Especially in the situation where you want to handle multiple errors, Variant types are a great abstraction to use.++Variant types are sometimes called '_polymorphic_ variants' for disambiguation. They are also commonly known as (open) unions, coproducts or extensible sums.++## General Usage++### Setup++The modules in this library are intended to be used qualified:++```haskell ignore+import Vary (Vary, (:|))+import qualified Vary++import Vary.VEither (VEither(VLeft, VRight))+import qualified Vary.VEither as VEither+```++The library is intended to be used with the following extensions active:++```haskell top:0+{-# LANGUAGE GHC2021 #-} -- Of these, Vary uses: TypeApplications, TypeOperators, FlexibleContexts+{-# LANGUAGE DataKinds #-}+```++### Simple usage++You can construct `Vary` using `Vary.from`:++```haskell+int_or_string :: Bool -> Vary '[Int, String]+int_or_string bool = + if bool then + Vary.from @Int 42+ else+ Vary.from @String "hello world"++-- You can also use the more general type,+-- which allows you to use a function to insert elements into any variant+-- that happens to contain the given type(s), +-- regardless of order or what other types are in its list:+int_or_string2 :: (Int :| l, String :| l) => Bool -> Vary l+int_or_string2 bool = + if bool then + Vary.from @Int 69+ else+ Vary.from @String "I like cheese"++```++You can check whether a particular variant is inside and attempt to extract it using `Vary.into`:++```haskell+maybe_an_int :: Bool -> Maybe Int+maybe_an_int bool = Vary.into @Int (int_or_string bool)+```++And you can match the various possibilities using `Vary.on`, together with `Vary.exhaustiveCase` if you've handled all cases:++```haskell+stringify vary = + vary & + ( Vary.on @String (\string -> "Found a string: " <> show string)+ $ Vary.on @Int (\int -> "Found an int: " <> show int)+ $ Vary.exhaustiveCase+ )+```++It is generally recommended to keep the type generic at the place you construct a `Vary` (using `Vary.from`), and make the type concrete at the place you use it (for instance when using `Vary.on` or `Vary.into`).++This way, the construction functions can be used in any context, regardless of what other possibilities the caller might want to handle. (See also the 'motivation' example below).++In some cases you already have a concrete `Vary` type, and you want to pass it to or return it from a function expecting another shape (different order of elements, having less or more elements). In those cases, `Vary.morph` will help you out :-).++### VEither++You have now seen `Vary`. There is the closely related type `VEither`. Its runtime representation is the same. The difference between `Vary (a : errs)` and `VEither errs a` is that while `Vary` considers all types in its list of possibilities to be equal, `VEither` considers the `a` to be a 'success' (`VRight`), whereas any of the types in `errs` are considered 'errors' (`VLeft`).++This means that `VEither` can implement `Functor`, `Applicative`, `Monad`, `Foldable`, `Traversable`, `Generic` and a few other fun typeclasses, making it really easy to use for error handling.++## Motivation: Why is 'Vary' useful?++ Say we are writing an image thumbnailing service.++ * Given an image URL+ * We attempt to download it.+ * This can fail, because the URL is incorrect;+ * Or the URL /is/ correct but the server could not be reached (in which case we want to re-try later);+ * Or the server /could/ be reached, but downloading took longer than a set time limit.++ * We pass it to a thumbnailing program.+ * This can fail, because the downloaded file might turn out actually not to be a valid image file (PNG or JPG);+ * Or even if the downloaded file /is/ an image, it might have a much too high resolution to attempt to read;+++ The first instinct might be to write dedicated sum types for these errors like so:++```haskell+data Image = Image+ deriving (Eq, Show)++data DownloaderError1+ = IncorrectUrl1+ | ServerUnreachable1+ | DownloadingTimedOut1 + deriving (Eq, Ord, Show)++data ThumbnailError1+ = NotAnImage1+ | TooBigImage1+ deriving (Eq, Ord, Show)++download1 :: String -> Either DownloaderError1 Image+download1 url = + -- Pretend we're doing hard work here+ Right Image++thumbnail1 :: Image -> Either ThumbnailError1 Image+thumbnail1 image = + -- Pretend we're huffing and puffing+ Right Image+```++But if we try to plainly combine these two functions, we get a compiler error:++```haskell+thumbnailService1 url = do+ image <- download1 url+ thumb <- thumbnail1 image+ pure thumb+```++```+error:+ • Couldn't match type ‘ThumbnailError’ with ‘DownloaderError’+ Expected: Either DownloaderError Image+ Actual: Either ThumbnailError Image+```+++We could \'solve\' this problem by adding yet another manual error type:++```haskell+data ThumbnailServiceError1+ = DownloaderError1 DownloaderError1+ | ThumbnailError1 ThumbnailError1+ deriving (Eq, Ord, Show)++thumbnailService2 :: String -> Either ThumbnailServiceError1 Image+thumbnailService2 url = do+ image <- first DownloaderError1 $ download1 url+ thumb <- first ThumbnailError1 $ thumbnail1 image+ pure thumb+```+++This 'works', although already we can see that we're doing a lot of manual ceremony to pass the errors around.++And wait! We wanted to re-try in the case of a `ServerUnreachable` error!++```haskell+waitAndRetry = undefined :: Word -> (() -> a) -> a++thumbnailServiceRetry2 :: String -> Either ThumbnailServiceError1 Image+thumbnailServiceRetry2 url = + case download1 url of+ Left ServerUnreachable1 -> waitAndRetry 10 (\_ -> thumbnailServiceRetry2 url) + Left other -> Left (DownloaderError1 other)+ Right image -> do+ thumb <- first ThumbnailError1 $ thumbnail1 image+ pure thumb+```++We now see:++- Even inside `thumbnailService` there now is quite a bit of ceremony + w.r.t. wrapping,unwrapping and mapping between error types.+- Callers will be asked to pattern match on the @ServerUnreachable@ error case,+ even though that case will already be handled inside the `thumbnailService` function itself!+- Imagine what happens when using this small function in a bigger system with many more errors!+ Do you keep defining more and more wrapper types for various combinations of errors?++#### There is a better way!++With the `Vary` and related `Vary.VEither.VEither` types, you can mix and match individual errors (or other types) at the places they are used.++```haskell top:2+import Vary (Vary, (:|))+import qualified Vary+import Vary.VEither (VEither(..))+import qualified Vary.VEither as VEither+```+```haskell+data IncorrectUrl2 = IncorrectUrl2 deriving (Eq, Ord, Show)+data ServerUnreachable2 = ServerUnreachable2 deriving (Eq, Ord, Show)+data DownloadingTimedOut2 = DownloadingTimedOut2 deriving (Eq, Ord, Show)++data NotAnImage2 = NotAnImage2 deriving (Eq, Ord, Show)+data TooBigImage2 = TooBigImage2 deriving (Eq, Ord, Show)++download :: (ServerUnreachable2 :| err, IncorrectUrl2 :| err) => String -> VEither err Image+download url = + -- Pretend a lot of network communication happens here+ VRight Image++thumbnail :: (NotAnImage2 :| err, TooBigImage2 :| err) => Image -> VEither err Image+thumbnail image = + -- Pretend this is super hard+ VRight Image+```++Here is the version without the retry:++```haskell+thumbnailService :: String -> VEither [ServerUnreachable2, IncorrectUrl2, NotAnImage2, TooBigImage2] Image+thumbnailService url = do+ image <- download url+ thumb <- thumbnail image+ pure thumb+```++And here is all that needed to change to have a retry:++```haskell+thumbnailServiceRetry :: String -> VEither [IncorrectUrl2, NotAnImage2, TooBigImage2] Image+thumbnailServiceRetry url = do+ image <- download url + & VEither.onLeft (\ServerUnreachable2 -> waitAndRetry 10 (\_ -> thumbnailServiceRetry url)) id+ thumb <- thumbnail image+ pure thumb+```++- No more wrapper type definitions!+- Handing an error makes it disappear from the output type!++## Why anoher Variant library?++I am aware of the following Haskell libraries offering support for Variant types already:++- [vinyl](https://hackage.haskell.org/package/vinyl)+- [extensible](https://hackage.haskell.org/package/extensible)+- [freer](https://hackage.haskell.org/package/freer)+- [fastsum](https://hackage.haskell.org/package/fastsum)+- [union](https://hackage.haskell.org/package/union)+- [open-union](https://hackage.haskell.org/package/open-union)+- [world-peace](https://hackage.haskell.org/package/world-peace)+- [haskus-utils-variant](https://hackage.haskell.org/package/haskus-utils-variant)++Vary improves upon them in the following ways:++- Function names in these libraries are long and repetitive, and often seem to be very different from names used elsewhere in `base` or the community.+ - `Vary` is intended to be used `qualified`, making the function names short and snappy, and allowing re-use of names like `map`, `from`, `on` and `into`.+- Many libraries define their variant type using a [Higher Kinded Data](https://reasonablypolymorphic.com/blog/higher-kinded-data/) pattern. This is really flexible, but not easy on the eyes.+ - `Vary`'s type is readable, which is what you want for the common case of using them for error handling.+ - It also means less manual type signatures are needed :-).+- Many libraries (exceptions: `fastsum`, `haskus`) define their variant as a GADT-style linked-list-like datatype. The advantage is that you need no `unsafeCoerce` anywhere. The disadvantage is that this has a huge runtime overhead.+ - `Vary` uses a single (unwrapped, strict) Word for the tag. GHC is able to optimize this representation very well!+ - Conversion between different variant shapes are also constant-time, as only this tag number needs to change.+- With the exception of `world-peace` and `haskus`, documentation of the libraries is very sparse.+ - All of the functions in `Vary` are documented and almost all of them have examples. ([Doctested](https://github.com/martijnbastiaan/doctest-parallel#readme) by the way!)+- The libraries try to make their variant be 'everything it can possibly be' and provide not only functions to work with variants by type, but also by index, popping, pushing, concatenating, handling all cases using a tuple of functions, etc. This makes it hard for a newcomer to understand what to use when.+ - `Vary` intentionally only exposes functions to work _by type_.+ - There is _one_ way to do case analysis of a `Vary`, namely using `Vary.on`. Only one thing to remember!+ - Many shape-modification functions were combined inside `Vary.morph`, so you only ever need that one!+ - Only the most widely-useful functions are provided in `Vary` itself. There are some extra functions in `Vary.Utils` which are intentionally left out of the main module to make it more digestible for new users. +- Libraries are already many years old (with no newer updates), and so they are not using any of the newer GHC extensions or inference improvements.+ - `Vary` makes great use of the `GHC2021` group of extensions, TypeFamilies and the `TypeError` construct to make most type errors disappear and for the few that remain it should be easy to understand how to fix them.++## Acknowledgements++This library stands on the shoulders of giants.++In big part it was inspired by the great Variant abstraction [which exists in PureScript](https://pursuit.purescript.org/packages/purescript-variant/8.0.0) (and related [VEither](https://pursuit.purescript.org/packages/purescript-veither/1.0.5)).++Where PureScript has a leg up over Haskell is in its support of row types. To make the types nice to use in Haskell even lacking row typing support was a puzzle in which the [Effectful](https://github.com/haskell-effectful/effectful) library gave great inspiration (and some type-level trickery was copied essentially verbatim from there.)++Finally, a huge shoutout to the pre-existing Variant libraries in Haskell. Especially to [haskus-utils-variant](https://hackage.haskell.org/package/haskus-utils-variant) and [world-peace](https://hackage.haskell.org/package/world-peace) and the resources found in [this blog post](https://functor.tokyo/blog/2019-07-11-announcing-world-peace) by world-peace's author.++## Is it any good?++Yes ;-)++<!-- +The following is executed by the README test runner,+but we don't want it to be visible to human readers:++```haskell top:1+{-# OPTIONS_GHC -fdefer-type-errors #-} -- We want to show some incorrect examples!+module Main where++import Test.Hspec (hspec, describe, it, shouldBe)+import Test.ShouldNotTypecheck (shouldNotTypecheck)+import Data.Bifunctor (first)+import Data.Function ((&))++```++```haskell+main :: IO ()+main = hspec $ do+ describe "simple examples" $ do+ it "string_or_int" $ do+ int_or_string True `shouldBe` (Vary.from @Int 42)+ int_or_string False `shouldBe` (Vary.from @String "hello world")+ it "maybe_an_int" $ do+ maybe_an_int True `shouldBe` (Just 42)+ maybe_an_int False `shouldBe` Nothing+ it "stringify" $ do+ stringify (int_or_string2 True) `shouldBe` "Found an int: 69"+ stringify (int_or_string2 False) `shouldBe` "Found a string: \"I like cheese\""+++ describe "motivating example" $ do+ describe "bad v1" $ do+ it "should not typecheck" $+ shouldNotTypecheck (thumbnailService1)+ describe "bad v2" $ do+ it "should work (but be verbose)" $+ thumbnailService2 "http://example.com" `shouldBe` (Right Image)+ describe "bad v2 (wth retry)" $ do+ it "should work (but be super verbose)" $+ thumbnailServiceRetry2 "http://example.com" `shouldBe` (Right Image)+ describe "nice" $ do+ it "should work nicely" $+ thumbnailService "http://example.com" `shouldBe` (VRight Image)+ describe "nice (with retry)" $ do+ it "should work nicely" $+ thumbnailServiceRetry "http://example.com" `shouldBe` (VRight Image)+```+-->
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Vary.hs view
@@ -0,0 +1,509 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE NoStarIsType #-}++module Vary+ ( -- * General Usage+ -- $setup+ -- $motivating_example+ -- $vary_and_exceptions++ -- * Core type definition+ Vary,+ (:|),++ -- * Construction and Destruction:+ from,+ into,+ intoOnly,++ -- * case analysis ("pattern matching"):++ -- |+ -- Vary does not support traditional pattern matching,+ -- because GHC is not able to check them for exhaustiveness.+ --+ -- Instead, Vary supports the next best thing: building up a pattern match using the 'on' combinator.+ on,+ exhaustiveCase,+ defaultCase,+ pop,++ -- * Transforming+ mapOn,+ morph,+ morphed,+ )+where++import Control.Monad (guard)+import Data.Kind+import GHC.TypeLits+import Unsafe.Coerce (unsafeCoerce)+import Vary.Core (Vary (..))+import Vary.Utils++-- $setup+--+-- == Setup+--+-- This module is intended to be used qualified:+--+-- >>> import Vary (Vary, (:|))+-- >>> import qualified Vary+-- +-- And for many functions, it is useful or outright necessary to enable the following extensions:+--+-- >>> :set -XGHC2021+-- >>> :set -XDataKinds+--+-- Finally, some example snippets in this module make use of 'Data.Function.&', the left-to-right function application operator.+--+-- >>> import Data.Function ((&))++{- $motivating_example++ == Motivating Example++ Say we are writing an image thumbnailing service.++ * Given an image URL++ * We attempt to download it.++ * This can fail, because the URL is incorrect;++ * Or the URL /is/ correct but the server could not be reached (in which case we want to re-try later);++ * Or the server /could/ be reached, but downloading took longer than a set time limit.++ * We pass it to a thumbnailing program.++ * This can fail, because the downloaded file might turn out actually not to be a valid image file (PNG or JPG);++ * Or even if the downloaded file /is/ an image, it might have a much too high resolution to attempt to read;+++ The first instinct of many Haskell programmers is to write dedicated sum types for these errors like so:++>>> import Data.Bifunctor (first)+>>> data Image = Image+>>>+>>> data DownloaderError = IncorrectUrl | ServerUnreachable | DownloadingTimedOut deriving (Eq, Ord, Show)+>>> data ThumbnailError = NotAnImage | TooBigImage deriving (Eq, Ord, Show)+>>>+>>> download = undefined :: String -> Either DownloaderError Image+>>>+>>> thumbnail = undefined :: Image -> Either ThumbnailError Image++But if we try to plainly combine these two functions, we get a compiler error:++>>> :{+ thumbnailService url = do+ image <- download url+ thumbnail <- thumbnail image+ pure thumbnail+:}+...+...• Couldn't match type ‘ThumbnailError’ with ‘DownloaderError’+...++We could \'solve\' this problem by adding yet another manual error type:++>>> data ThumbnailServiceError = DownloaderError DownloaderError | ThumbnailError ThumbnailError deriving (Eq, Ord, Show)+>>> :{+ thumbnailService :: String -> Either ThumbnailServiceError Image+ thumbnailService url = do+ image <- first DownloaderError $ download url+ thumb <- first ThumbnailError $ thumbnail image+ pure thumb+:}++This \'works\', although already we can see that we're doing a lot of manual ceremony to pass the errors around.++And wait! We wanted to re-try in the case of a `ServerUnreachable` error!++>>> waitAndRetry = undefined :: Word -> (() -> a) -> a+>>> :{+ thumbnailService :: String -> Either ThumbnailServiceError Image+ thumbnailService url = + case download url of+ Left ServerUnreachable -> waitAndRetry 10 (\_ -> thumbnailService url) + Left other -> Left (DownloaderError other)+ Right image -> do+ thumb <- first ThumbnailError $ thumbnail image+ pure thumb+:}++We now see:++- Even inside @thumbnailService@ there now is quite a bit of ceremony + w.r.t. wrapping,unwrapping and mapping between error types.+- Callers will be asked to pattern match on the @ServerUnreachable@ error case,+ even though that case will already be handled inside the @thumbnailService@ function itself!+- Imagine what happens when using this small function in a bigger system with many more errors!+ Do you keep defining more and more wrapper types for various combinations of errors?++==== There is a better way!++With the `Vary` and related `Vary.VEither.VEither` types, you can mix and match individual errors (or other types) at the places they are used.++- No more wrapper type definitions!+- Handing an error makes it go away from the outcome type!++>>> import Vary.VEither (VEither)+>>> import qualified Vary.VEither as VEither+>>> data Image = Image deriving(Show)+>>>+>>> data IncorrectUrl = IncorrectUrl deriving (Eq, Ord, Show)+>>> data ServerUnreachable = ServerUnreachable deriving (Eq, Ord, Show)+>>> data DownloadingTimedOut = DownloadingTimedOut deriving (Eq, Ord, Show)+>>>+>>> data NotAnImage = NotAnImage deriving (Eq, Ord, Show)+>>> data TooBigImage = TooBigImage deriving (Eq, Ord, Show)+>>>+>>> download = undefined :: (ServerUnreachable :| err, IncorrectUrl :| err) => String -> VEither err Image+>>> thumbnail = undefined :: (NotAnImage :| err, TooBigImage :| err) => Image -> VEither err Image+>>>+>>> waitAndRetry = undefined :: Word -> (() -> a) -> a+>>>++Here is the version without the retry:++>>> :{+thumbnailService :: String -> VEither [ServerUnreachable, IncorrectUrl, NotAnImage, TooBigImage] Image+thumbnailService url = do+ image <- download url+ thumb <- thumbnail image+ pure thumb+:}++And here is all that needed to change to have a retry:++>>> :{+thumbnailService :: String -> VEither [IncorrectUrl, NotAnImage, TooBigImage] Image+thumbnailService url = do+ image <- download url + & VEither.onLeft (\ServerUnreachable -> waitAndRetry 10 (\_ -> thumbnailService url)) id+ thumb <- thumbnail image+ pure thumb+:}++-}++-- $vary_and_exceptions+--+-- == Vary and Exceptions #vary_and_exceptions#+--+-- 'Vary' implements 'Control.Exception.Exception', +-- and is an /excellent/ type to use with 'Control.Exception.throw' and 'Control.Exception.catch'.+--+-- >>> import Control.Exception+-- >>> no_xyzzy = Vary.from (NoMethodError "xyzzy") :: Vary '[NoMethodError, ArithException]+-- >>> divby0 = Vary.from DivideByZero :: Vary '[NoMethodError, ArithException]+--+-- >>> throw no_xyzzy `catch` \(e :: Vary '[NoMethodError, ArithException]) -> putStrLn ("Caught: `" <> show e <> "`")+-- Caught: `Vary.from @NoMethodError xyzzy`+--+-- === Catching individual errors of a thrown 'Vary'+--+-- 'Control.Exception.toException' is implemented to throw the particular /internal/ type.+--+-- This means that you can catch any of the particular individual possibilities of a thrown Vary if you like,+-- and have the others bubble up:+--+-- >>> throw no_xyzzy `catch` \(e :: NoMethodError) -> putStrLn ("Caught: `" <> show e <> "`")+-- Caught: `xyzzy`+--+-- >>> throw divby0 `catch` \(e :: NoMethodError) -> putStrLn ("Caught: `" <> show e <> "`")+-- *** Exception: divide by zero+--+-- === Catching groups of (individually thrown) errors+--+-- Also, 'Control.Exception.fromException' is implemented to /match/ any of the contained possibilities:+--+-- >>> catcher inner = inner `catch` \(e :: Vary '[NoMethodError, ArithException]) -> putStrLn ("Caught: `" <> show e <> "`")+--+-- So not only is the following exception caught:+--+-- >>> vary = Vary.from (NoMethodError "plover") :: Vary '[NoMethodError, ArithException]+-- >>> catcher (throw vary)+-- Caught: `Vary.from @NoMethodError plover`+--+-- But it will also catch a thrown @ArithException@+--+-- >>> catcher (throw DivideByZero)+-- Caught: `Vary.from @ArithException divide by zero`+--+-- or a thrown @NoMethodError@!+--+-- >>> catcher (throw (NoMethodError "plugh"))+-- Caught: `Vary.from @NoMethodError plugh`+--+-- /(and other exceptions of course still bubble up)/+--+-- >>> catcher (throw AllocationLimitExceeded)+-- *** Exception: allocation limit exceeded+++-- | Builds a Vary from the given value.+--+-- >>> let thingy :: Vary [Bool, Char]; thingy = Vary.from 'a'+-- >>> thingy+-- Vary.from @Char 'a'+--+-- In the case of number literals or (with OverloadedStrings or OverloadedLists enabled) string or list literals,+-- it might be necessary to include a TypeApplication.+-- In most other cases, GHC is able to infer which possibility to use (though you might still like type applications even here for improved readability).+--+-- >>> Vary.from @Int 42 :: Vary [Int, String]+-- Vary.from @Int 42+--+-- In the case of the Vary contains duplicate types,+-- the first matching type index is used.+from ::+ forall a l. a :| l => a -> Vary l+{-# INLINE from #-}+from = fromAt @(IndexOf a l)++-- | Attempts to turn the Vary back into a particular type.+--+-- This might fail since the Vary might actually contain another possibility,+-- which is why a `Maybe` is returned.+--+-- If you have a single possibility, you can use `intoOnly` instead.+--+-- == Polymorphic functions+--+-- If you pass the result to a polymorphic function, GHC might not be able to infer which result type you'd like to try to extract.+-- Indicate the desired result type using a TypeApplication:+--+-- >>> let vary = Vary.from @Bool True :: Vary [Bool, String]+-- >>> Vary.into @Bool vary+-- Just True+--+-- == Type errors+-- Sometimes you might see nasty long type errors, containing the string+-- `Type_List_Too_Vague___Please_Specify_Prefix_Of_List_Including_The_Desired_Type's_Location`.+--+-- This happens when other parts of your code keep the type list fully abstract (only use the `:|` constraint).+--+-- You can fix it by either giving a type to an intermediate value,+-- or by passing a second type application to this function:+--+-- >>> let vary = if True then Vary.from True else Vary.from 'a' -- Inferred type: `Bool :| l, Char :| l => Vary l`+-- >>> Vary.into @Bool @(Char : Bool : _) vary+-- Just True+--+-- As you can see from the above example, it is often not necessary to specify the /full/ type list.+-- A prefix is commonly enough.+into :: forall a l. (a :| l) => Vary l -> Maybe a+{-# INLINE into #-}+into = intoAt @(IndexOf a l)++-- | Extract the value of a variant with one possibility.+--+-- A variant with only a single possibility+-- can always be safely turned back into this one type.+--+-- If you have multiple possibilities, use `into`.+intoOnly :: forall a. Vary '[a] -> a+{-# INLINE intoOnly #-}+intoOnly (Vary _ val) = unsafeCoerce val++-- | Base case of an exhaustive pattern match. +--+-- Use it together with `on`, +-- or whenever you have an empty `Vary '[]` that you need to get rid of.+-- (Like in a recursive typeclass definition. See "Vary".'pop')+--+-- Since it is impossible to actually /construct/ a value of the type @Vary '[]@,+-- we can "turn it into anything", just like `Data.Void.absurd`.+exhaustiveCase :: forall anything. Vary '[] -> anything+{-# INLINE exhaustiveCase #-}+exhaustiveCase _vary =+ error "Somehow someone got their hands on a runtime value of type Vary '[]. This should be impossible, so someone did a bad unsafeCoerce somewhere!"++-- | Base case of a non-exhaustive pattern match. Use it together with `on`.+--+-- If you've handled the variants you like and have some left,+-- you can specify a default fallback value using `defaultCase`.+--+-- Indeed, this function is just another name for `const`.+defaultCase :: forall a l. a -> Vary l -> a+{-# INLINE defaultCase #-}+defaultCase = const++-- | Extend a smaller `Vary` into a bigger one, change the order of its elements, or get rid of duplicates.+--+-- === Extend a smaller `Vary`:+-- >>> small = Vary.from True :: Vary '[Bool]+-- >>> big = Vary.morph small :: Vary [Bool, Int, String]+-- >>> big+-- Vary.from @Bool True+--+-- === Reorder elements:+-- >>> boolfirst = Vary.from @Int 42 :: Vary [Bool, Int]+-- >>> intfirst = Vary.morph boolfirst :: Vary [Int, Bool]+-- >>> intfirst+-- Vary.from @Int 42+--+-- === Get rid of duplicate elements:+-- >>> duplicates = Vary.from @Int 69 :: Vary [Int, Int, Int]+-- >>> noduplicates = Vary.morph duplicates :: Vary '[Int]+-- >>> noduplicates+-- Vary.from @Int 69+--+-- === Type applications+-- Morph intentionally takes the result type list as first type-application parameter.+-- This allows you to write above examples in this more concise style instead:+--+-- >>> big = Vary.morph @[Bool, Int, String] small+-- >>> intfirst = Vary.morph @[Int, Bool] boolfirst+-- >>> noduplicates = Vary.morph @'[Int] duplicates+--+--+-- == Efficiency+-- This is a O(1) operation, as the tag number stored in the variant is+-- changed to the new tag number.+--+-- In many cases GHC can even look through the old->new Variant structure entirely,+-- and e.g. inline the variant construction all-together.+morph :: forall ys xs. (Subset xs ys) => Vary xs -> Vary ys+morph = morph' @xs @ys++fromAt ::+ forall (n :: Nat) (l :: [Type]).+ ( KnownNat n+ ) =>+ Index n l ->+ Vary l+{-# INLINE fromAt #-}+fromAt a = Vary (natValue @n) (unsafeCoerce a)++intoAt ::+ forall (n :: Nat) (l :: [Type]).+ ( KnownNat n+ ) =>+ Vary l ->+ Maybe (Index n l)+{-# INLINE intoAt #-}+intoAt (Vary t a) = do+ guard (t == natValue @n)+ return (unsafeCoerce a)++-- | Handle a particular variant possibility.+--+-- This is the main way to do case analysis (or 'deconstruct') a variant.+--+-- Use it together with `exhaustiveCase` if you handle all possibilities,+-- or `defaultCase` if you don't want to.+--+-- Even though in many cases GHC is able to infer the types,+-- it is a good idea to combine it with `TypeApplications`:+--+-- Note that by doing so, GHC can infer the type of the function without problems:+--+-- >>> :{+-- example vary =+-- vary &+-- ( Vary.on @Bool show+-- $ Vary.on @Int (\x -> show (x + 1))+-- $ Vary.defaultCase "other value"+-- )+-- :}+--+-- >>> :t example+-- example :: Vary (Bool : Int : l) -> String+on :: forall a b l. (a -> b) -> (Vary l -> b) -> Vary (a : l) -> b+{-# INLINE on #-}+on thisFun restFun vary =+ case Vary.into @a vary of+ Just val -> thisFun val+ Nothing ->+ restFun (coerceHigher vary)+ where+ -- Invariant: does not contain @a+ {-# INLINE coerceHigher #-}+ coerceHigher :: Vary (a : l) -> Vary l+ coerceHigher (Vary idx val) =+ unsafeCoerce (Vary (idx - 1) val)++-- | Execute a function expecting a larger (or differently-ordered) variant+-- with a smaller (or differently-ordered) variant,+-- by calling `morph` on it before running the function.+morphed :: forall a b res. (Subset a b) => (Vary b -> res) -> Vary a -> res+{-# INLINE morphed #-}+morphed fun = fun . morph++-- | Run a function on one of the variant's possibilities, keeping all other possibilities the same.+--+-- This is the generalization of functions like Either's `Data.Either.Extra.mapLeft` and `Data.Either.Extra.mapRight`.+--+-- If you want to map a polymorphic function like `show` which could match more than one possibility,+-- use a TypeApplication to specify the desired possibility to match:+--+-- >>> :{+-- (Vary.from @Int 42 :: Vary [Int, Bool] )+-- & Vary.mapOn @Bool show -- Vary [Int, String]+-- & Vary.mapOn @Int show -- Vary [String, String]+-- :}+-- Vary.from @[Char] "42"+--+-- If you end up with a variant with multiple duplicate possibilities, use `morph` to join them:+--+-- >>> :{ +-- (Vary.from True :: Vary [Char, Int, Bool])+-- & Vary.mapOn @Bool show -- Vary [Char, Int, String]+-- & Vary.mapOn @Int show -- Vary [Char, String, String]+-- & Vary.mapOn @Char show -- Vary [String, String, String]+-- & Vary.morph @'[String] -- Vary '[String]+-- & Vary.intoOnly -- String+-- :}+-- "True"+--+-- Note that if you end up handling all cases of a variant, you might prefer using `Vary.on` and `Vary.exhaustiveCase` instead.+--+-- == Generic code+--+-- It is possible to use the most general type of this function in your own signatures;+-- To do this, add the `Mappable` constraint (exposed from `Vary.Utils`) +-- to relate the input variant with the output variant.+--+-- >>> import qualified Data.Char+-- >>> :{+-- example4 :: (Vary.Utils.Mappable Int Bool xs ys, Vary.Utils.Mappable Char Int ys zs) => Vary xs -> Vary zs+-- example4 vary =+-- vary+-- & Vary.mapOn @Int (\x -> x > 0)+-- & Vary.mapOn @Char Data.Char.ord+-- :}+--+-- == Duplicate possibilities+-- Vary.mapOn will only work on the first instance of the type that is encountered.+-- This is only a problem if a possibility is in the list multiple times;+-- be sure to `Vary.morph` duplicate possibilities away if needed.+mapOn :: forall a b xs ys. (Mappable a b xs ys) => (a -> b) -> Vary xs -> Vary ys+mapOn fun vary@(Vary tag val) = + case into @a vary of+ Just a -> from @b (fun a)+ Nothing -> (Vary tag val)
+ src/Vary/Core.hs view
@@ -0,0 +1,136 @@+{-# LANGUAGE GHC2021 #-}+{-# OPTIONS_HADDOCK not-home #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE MonoLocalBinds #-}+module Vary.Core (Vary (..), pop) where++import Data.Kind (Type)+import GHC.Exts (Any)+import qualified Unsafe.Coerce as Data.Coerce+import Control.DeepSeq (NFData (..))+import Control.Exception (Exception(..))+import Data.Typeable (Typeable, typeOf)++-- $setup+-- >>> :set -XGHC2021+-- >>> :set -XDataKinds+-- >>> import Vary (Vary, (:|))+-- >>> import qualified Vary++-- | Vary, contains one value out of a set of possibilities+--+-- Vary is what is known as a /Variant/ type.+-- This is also known as an /open union/ or /coproduct/, among other names.+--+-- You can see it as the generalization of `Either`.+-- Conceptually, these are the same:+--+-- > Vary [a, b, c, d, e]+-- > Either a (Either b (Either c (Either d e)))+--+-- However, compared to a deeply nested `Either`, `Vary` is:+--+-- - Much easier to work with;+-- - Much more efficient, as a single (strict) word is used for the tag.+--+-- `Vary`'s can be constructed with "Vary".`Vary.from` and values can be extracted using "Vary".`Vary.into` and "Vary".'Vary.on' .+data Vary (possibilities :: [Type]) = Vary {-# UNPACK #-} !Word Any++emptyVaryError :: forall anything. String -> Vary '[] -> anything+emptyVaryError name = error (name <> " was called on empty Vary '[]")+++-- | Attempts to extract a value of the first type from the `Vary`.+--+-- If this failed, we know it has to be one of the other possibilities.+--+-- This function can also be seen as turning one layer of `Vary` into its isomorphic `Either` representation.+--+-- This function is not often useful in 'normal' code, but /super/ useful in generic code where you want to recurse on the variant's types.+--+-- For instance when implementing a typeclass for any `Vary` whose elements implement the typeclass:+--+--+-- > instance Show (Vary '[]) where+-- > show = Vary.exhaustiveCase+-- >+-- > instance (Show a, Show (Vary as)) => Show (Vary (a : as)) where+-- > show vary = case Vary.pop vary of+-- > Right val -> "Vary.from " <> show val+-- > Left other -> show other+--+-- To go the other way: +--+-- - Use "Vary".`Vary.morph` to turn @Vary as@ back into @Vary (a : as)@+-- - Use "Vary".`Vary.from` to turn @a@ back into @Vary (a : as)@+pop :: Vary (a : as) -> Either (Vary as) a+{-# INLINE pop #-}+pop (Vary 0 val) = Right (Data.Coerce.unsafeCoerce val)+pop (Vary tag val) = Left (Data.Coerce.unsafeCoerce (Vary (tag - 1) val))++instance Eq (Vary '[]) where+ (==) = emptyVaryError "Eq.(==)"++instance (Eq a, Eq (Vary as)) => Eq (Vary (a : as)) where+ {-# INLINE (==) #-}+ a == b = pop a == pop b++instance Ord (Vary '[]) where+ compare = emptyVaryError "Ord.compare"++instance (Ord a, Ord (Vary as)) => Ord (Vary (a : as)) where+ {-# INLINE compare #-}+ l `compare` r = pop l `compare` pop r++instance Show (Vary '[]) where+ show = emptyVaryError "Show.show"++-- | `Vary`'s 'Show' instance only works for types which are 'Typeable'+--+-- This allows us to print the name of the type which+-- the current value is of.+--+-- >>> Vary.from @Bool True :: Vary '[Int, Bool, String]+-- Vary.from @Bool True+--+-- >>> Vary.from @(Maybe Int) (Just 1234) :: Vary '[Maybe Int, Bool]+-- Vary.from @(Maybe Int) (Just 1234)+instance (Typeable a, Show a, Show (Vary as)) => Show (Vary (a : as)) where+ showsPrec d vary = case pop vary of+ Right val ->+ showString "Vary.from " . + showString "@" . + showsPrec (d+10) (typeOf val) . + showString " " . + showsPrec (d+11) val+ Left other -> showsPrec d other++instance NFData (Vary '[]) where+ rnf = emptyVaryError "NFData.rnf"++instance (NFData a, NFData (Vary as)) => NFData (Vary (a : as)) where+ {-# INLINE rnf #-}+ rnf vary = rnf (pop vary)+++instance (Typeable (Vary '[]), Show (Vary '[])) => Exception (Vary '[]) where++-- | See [Vary and Exceptions](#vary_and_exceptions) for more info.+instance (Exception e, Exception (Vary errs), Typeable errs) => Exception (Vary (e : errs)) where+ displayException vary = + case pop vary of+ Right val -> displayException val+ Left rest -> displayException rest++ toException vary = + case pop vary of+ Right val -> toException val+ Left rest -> toException rest+ + fromException some_exception = + case fromException @e some_exception of+ Just e -> Just (Vary 0 (Data.Coerce.unsafeCoerce e))+ Nothing -> case fromException @(Vary errs) some_exception of+ Just (Vary tag err) -> Just (Data.Coerce.unsafeCoerce (Vary (tag+1) err))+ Nothing -> Nothing
+ src/Vary/Utils.hs view
@@ -0,0 +1,224 @@+{-# LANGUAGE GHC2021 #-}+{-# OPTIONS_GHC -Wno-redundant-constraints #-}+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}+{-# HLINT ignore "Use camelCase" #-} -- <- We want a fun long type name with underscores for easier to read errors ;-)+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE TypeFamilyDependencies #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+module Vary.Utils(+ + -- * Useful in generic code+ (:|), + KnownPrefix(..), + Length, + Subset(..), + Index, + IndexOf, + Mappable, + pop,++ -- * Informational+ size,+ activeIndex,++ -- * Helper+ natValue, +) where++import Data.Kind (Constraint, Type)+import Data.Proxy (Proxy (..))+import GHC.TypeLits+ ( ErrorMessage (ShowType, Text, (:$$:), (:<>:)),+ KnownNat,+ Nat,+ TypeError,+ natVal,+ type (+),+ type (-),+ )+import Vary.Core (Vary (..), pop)++-- | Constrain `es` to be any type list containing `e`.+--+-- Useful to talk about variants generically without having to specify the exact type list right away.+--+-- For instance, the type of `Vary.from` is+--+-- > Vary.from :: (a :| l) => a -> Vary l+--+-- because we can use it to construct /any/ Vary as long as there is an @a@ somewhere in its list of types.+type (:|) e es = Member e es++-- | Returns the number of elements contained in this variant.+--+-- Does not actually use the runtime representation of the variant in any way.+size :: forall xs. (KnownNat (Length xs)) => Vary xs -> Word+size _ = natValue @(Length xs)++-- | Returns the currently active 'tag index' of the variant.+--+-- Not useful in normal code, but maybe nice in certaing debugging scenarios.+--+-- Note that this index changes whenever a variant is `Vary.morph`ed.+activeIndex :: Vary a -> Word+activeIndex (Vary idx _) = idx++++-- | Provide evidence that @xs@ is a subset of @es@.+class (KnownPrefix es) => Subset (xs :: [Type]) (es :: [Type]) where+ subsetFullyKnown :: Bool+ subsetFullyKnown =+ -- Don't show "minimal complete definition" in haddock.+ error "subsetFullyKnown"++ -- reifyIndices :: [Int]+ -- reifyIndices =+ -- -- Don't show "minimal complete definition" in haddock.+ -- error "reifyIndices"++ -- reifyIndicesVec :: UVector Int+ -- reifyIndicesVec =+ -- -- Don't show "minimal complete definition" in haddock.+ -- error "reifyIndicesVec"++ morph' :: Vary xs -> Vary ys+ morph' =+ -- Don't show "minimal complete definition" in haddock.+ -- Also, default for the empty instance :-)+ error "morph' was unexpectedly called"++-- If the subset is not fully known, make sure the subset and the base stack+-- have the same unknown suffix.+instance+ {-# INCOHERENT #-}+ ( KnownPrefix es,+ xs `IsUnknownSuffixOf` es+ ) =>+ Subset xs es+ where+ subsetFullyKnown = False++-- reifyIndices = []+-- {-# INLINE reifyIndicesVec #-}+-- reifyIndicesVec = UVector.empty++-- If the subset is fully known, we're done.+instance (KnownPrefix es) => Subset '[] es where+ subsetFullyKnown = True++-- reifyIndices = []+-- {-# INLINE reifyIndicesVec #-}+-- reifyIndicesVec = UVector.empty -- UVector.empty++instance (e :| es, Subset xs es) => Subset (e : xs) es where+ subsetFullyKnown = subsetFullyKnown @xs @es++ -- reifyIndices = natValue @(IndexOf e es) : reifyIndices @xs @es+ -- {-# INLINE reifyIndicesVec #-}+ -- reifyIndicesVec = UVector.fromList (natValue @(IndexOf e es) : reifyIndices @xs @es) -- UVector.cons (natValue @(IndexOf e es)) (reifyIndicesVec @xs @es)++ morph' (Vary 0 a) = Vary (natValue @(IndexOf e es)) a+ morph' (Vary n a) = morph' @xs @es (Vary (n - 1) a)++----++-- | Calculate length of a statically known prefix of @es@.+class KnownPrefix (es :: [Type]) where+ prefixLength :: Int++instance (KnownPrefix es) => KnownPrefix (e : es) where+ prefixLength = 1 + prefixLength @es++instance {-# INCOHERENT #-} KnownPrefix es where+ prefixLength = 0++----++-- | Require that @xs@ is the unknown suffix of @es@.+class (xs :: [k]) `IsUnknownSuffixOf` (es :: [k])++instance {-# INCOHERENT #-} (xs ~ es) => xs `IsUnknownSuffixOf` es++instance (xs `IsUnknownSuffixOf` es) => xs `IsUnknownSuffixOf` (e : es)++-- | Get list length+type family Length (xs :: [k]) :: Nat where+ Length xs = Length' 0 xs++type family Length' n (xs :: [k]) :: Nat where+ Length' n '[] = n+ Length' n (x ': xs) = Length' (n + 1) xs++natValue :: forall (n :: Nat) a. (KnownNat n, Num a) => a+{-# INLINEABLE natValue #-}+natValue = fromIntegral (natVal (Proxy :: Proxy n))++-- | Constraint to link the input and output lists together, without specifying any particular element order.+--+-- This allows us to defer type signatures until the final place the variant is used.+type Mappable a b xs ys = (a :| xs, b :| ys, ys ~ Mapped a b xs)++-- | Compute a HList where the type a was changed into b.+type family Mapped (a :: Type) (b :: Type) (as :: [Type]) = (bs :: [Type]) where+ Mapped a b (a ': as) = (b ': as)+ Mapped a b (x ': as) = x ': Mapped a b as+ Mapped a b l = TypeError (+ 'Text "Cannot map from " ':<>: 'ShowType a ':<>: 'Text " into " ':<>: 'ShowType b + :$$: 'Text "as it cannot be found in the list " ':<>: 'ShowType l)++-- | Get the first index of a type+type IndexOf (x :: k) (xs :: [k]) = IndexOf' (MaybeIndexOf x xs) x xs++-- | Get the first index of a type+type family IndexOf' (i :: Nat) (a :: k) (l :: [k]) :: Nat where+ IndexOf' 0 x l =+ TypeError+ ( 'ShowType x+ ':<>: 'Text " not found in list:"+ ':$$: 'Text " "+ ':<>: 'ShowType l+ )+ IndexOf' i _ _ = i - 1++-- | Get the first index (starting from 1) of a type or 0 if none+type family MaybeIndexOf (a :: k) (l :: [k]) where+ MaybeIndexOf x xs = MaybeIndexOf' 0 x xs++-- | Helper for MaybeIndexOf+type family MaybeIndexOf' (n :: Nat) (a :: k) (l :: [k]) where+ MaybeIndexOf' n x '[] = 0+ MaybeIndexOf' n x (x ': xs) = n + 1+ MaybeIndexOf' n x (y ': xs) = MaybeIndexOf' (n + 1) x xs++-- | Indexed access into the list+type Index (n :: Nat) (l :: [k]) = Type_List_Too_Vague___Please_Specify_Prefix_Of_List_Including_The_Desired_Type's_Location n l l++-- | We use this ridiculous name+-- to make it clear to the user when they see it in a type error+-- how to resolve that type error.+type family Type_List_Too_Vague___Please_Specify_Prefix_Of_List_Including_The_Desired_Type's_Location (n :: Nat) (l :: [k]) (l2 :: [k]) :: k where+ Type_List_Too_Vague___Please_Specify_Prefix_Of_List_Including_The_Desired_Type's_Location 0 (x ': _ ) _ = x+ Type_List_Too_Vague___Please_Specify_Prefix_Of_List_Including_The_Desired_Type's_Location n (_ ': xs) l2 = Type_List_Too_Vague___Please_Specify_Prefix_Of_List_Including_The_Desired_Type's_Location (n-1) xs l2+ Type_List_Too_Vague___Please_Specify_Prefix_Of_List_Including_The_Desired_Type's_Location n '[] l2 = TypeError ( 'Text "Index "+ ':<>: 'ShowType n+ ':<>: 'Text " out of bounds for list:"+ ':$$: 'Text " "+ ':<>: 'ShowType l2 )+++-- | Constraint: x member of xs+type family Member x xs :: Constraint where+ Member x xs = MemberAtIndex (IndexOf x xs) x xs++type MemberAtIndex i x xs =+ ( x ~ Index i xs,+ KnownNat i+ )++-- | Remove (the first) `a` in `l`+type family Remove (a :: k) (l :: [k]) :: [k] where+ Remove a '[] = '[]+ Remove a (a ': as) = as+ Remove a (b ': as) = b ': Remove a as
+ src/Vary/VEither.hs view
@@ -0,0 +1,272 @@+{-# LANGUAGE GHC2021 #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE TypeFamilies #-}+module Vary.VEither (+ -- * General Usage+ -- $setup++ -- * Core type definition+ VEither(VLeft, VRight), + -- * Conversion+ toVary, + fromVary,+ fromLeft,+ fromRight,+ toEither,+ fromEither,+ veither,+ intoOnly,++ -- * case analysis ("pattern matching"):++ -- |+ --+ -- Besides the 'VLeft' and 'VRight' patterns,+ -- 'VEither' supports a bunch of handy combinator functions,+ -- similar to "Vary".'Vary.on' and co.+ onLeft,+ onRight,++ -- * Transforming+ mapLeftOn,+ mapLeft,+ mapRight,+ morph,+ morphed,+) where++import Control.Category ((>>>))+import Control.DeepSeq (NFData (..))+import qualified Data.Either+import Data.Kind (Type)+import Vary.Core (Vary(..))+import Vary.Utils (Subset, Mappable)+import Vary ((:|))+import qualified Vary+import GHC.Generics++-- $setup+--+-- This module is intended to be used qualified:+--+-- >>> import Vary.VEither (VEither(VLeft, VRight))+-- >>> import qualified Vary.VEither as VEither+-- +-- And for many functions, it is useful or outright necessary to enable the following extensions:+--+-- >>> :set -XGHC2021+-- >>> :set -XDataKinds++newtype VEither (errs :: [Type]) a = VEither (Vary (a : errs))++-- | Turns the 'VEither' into a normal Vary, no longer considering the @a@ a \'preferred\' value.+toVary :: VEither errs a -> Vary (a : errs)+{-# INLINE toVary #-}+toVary (VEither vary) = vary++-- | Turns a 'Vary' into a 'VEither'. Now the @a@ is considered the \'preferred\' value.+fromVary :: Vary (a : errs) -> VEither errs a+{-# INLINE fromVary #-}+fromVary vary = VEither vary++-- | Turns a 'VEither' into a normal 'Either'.+toEither :: VEither errs a -> Either (Vary errs) a+{-# INLINE toEither #-}+toEither = toVary >>> Vary.pop++-- | Turns a normal 'Either' into a 'VEither'.+fromEither :: Either (Vary errs) a -> VEither errs a+{-# INLINE fromEither #-}+fromEither = Data.Either.either Vary.morph Vary.from >>> fromVary++-- | Shorthand to construct a 'VEither' from a single error value.+--+-- Instead of:+--+-- >>> (VLeft (Vary.from @Bool True)) :: VEither '[Bool] String+-- VLeft (Vary.from @Bool True) +--+-- You can just write:+--+-- >>> VEither.fromLeft @Bool True :: VEither '[Bool] String+-- VLeft (Vary.from @Bool True) +fromLeft :: forall err errs a. err :| errs => err -> VEither errs a+fromLeft = Vary.from @err >>> VLeft++-- | Construct a 'VEither' from an @a@.+--+-- Exists for symmetry with 'fromLeft'.+-- Indeed, this is just another name for 'VRight'.+fromRight :: forall a errs. a -> VEither errs a+fromRight = VRight++-- | Case analysis on a 'VEither'. Similar to 'Data.Either.either'.+--+-- See also "VEither".'mapLeft', "VEither".'mapLeftOn' and "VEither".'mapRight'.+veither :: (Vary errs -> c) -> (a -> c) -> VEither errs a -> c+veither f _ (VLeft x) = f x+veither _ g (VRight y) = g y++{-# COMPLETE VLeft, VRight #-}++pattern VLeft :: forall a errs. Vary errs -> VEither errs a+{-# INLINE VLeft #-}+pattern VLeft errs <- (toEither -> Left errs)+ where+ VLeft (Vary tag err) = VEither ((Vary (tag+1) err))++pattern VRight :: forall a errs. a -> VEither errs a+{-# INLINE VRight #-}+pattern VRight a <- (toEither -> Right a)+ where+ VRight a = VEither (Vary.from @a a)++onLeft :: forall err b errs a. (err -> b) -> (VEither errs a -> b) -> VEither (err : errs) a -> b+onLeft thiserrFun restfun ve = case ve of+ VLeft e -> Vary.on @err thiserrFun (\otherErr -> restfun (VLeft otherErr)) e+ VRight a -> restfun (VRight a)++onRight :: (a -> b) -> (VEither errs a -> b) -> VEither errs a -> b+onRight valfun restfun ve = case ve of+ VRight a -> valfun a+ VLeft err -> restfun (VLeft err)++-- | If you have a VEither which does not actually contain any errors,+-- you can be sure it always contains an @a@.+--+-- Similar to "Vary".'Vary.intoOnly'.+intoOnly :: forall a. VEither '[] a -> a+intoOnly (VRight a) = a+intoOnly (VLeft emptyVary) = Vary.exhaustiveCase emptyVary+++morph :: forall ys xs a. Subset (a : xs) (a : ys) => VEither xs a -> VEither ys a+morph = toVary >>> Vary.morph >>> fromVary++-- | Execute a function expecting a larger (or differently-ordered) variant+-- with a smaller (or differently-ordered) variant,+-- by calling `morph` on it before running the function.+morphed :: forall xs ys a res. Subset (a : xs) (a : ys) => (VEither ys a -> res) -> VEither xs a -> res+{-# INLINE morphed #-}+morphed fun = fun . morph++-- | Map a function over one of the error values inside the 'VEither'.+--+-- Any other 'VLeft' and also 'VRight' are kept untouched.+--+-- Similar to "Vary".'Vary.mapOn'.+mapLeftOn :: forall x y xs ys a. (Mappable x y xs ys) => (x -> y) -> VEither xs a -> VEither ys a+mapLeftOn _ (VRight val) = VRight val+mapLeftOn fun (VLeft err) = VLeft $ Vary.mapOn fun err++-- | Map a function over the 'VEither' if it contains a 'VLeft', otherwise leave it alone.+--+-- See also "VEither".'mapLeftOn', "VEither".'mapRight' and "VEither".'veither'.+--+mapLeft :: (Vary xs -> Vary ys) -> VEither xs a -> VEither ys a+mapLeft fun ve = case ve of + VRight a -> VRight a+ VLeft errs -> VLeft (fun errs)++-- | Map a function over the 'VEither' if it contains a 'VRight', otherwise leave it alone.+--+-- Exists for symmetry with "VEither".'mapLeft' and "VEither".'mapLeftOn'.+--+-- Indeed, it is just another name for 'fmap'.+--+-- See also "VEither".'veither'.+mapRight :: (x -> y) -> VEither errs x -> VEither errs y+mapRight fun ve = case ve of + VRight a -> VRight (fun a)+ VLeft errs -> VLeft errs++instance (Show a, Show (Vary errs)) => Show (VEither errs a) where+ show (VLeft errs) = "VLeft (" <> show errs <> ")"+ show (VRight a) = "VRight " <> show a++instance (Eq a, Eq (Vary errs)) => Eq (VEither errs a) where+ a == b = toVary a == toVary b++instance (Ord a, Ord (Vary errs)) => Ord (VEither errs a) where+ compare a b = compare (toVary a) (toVary b)++instance (NFData a, NFData (Vary errs)) => NFData (VEither errs a) where+ rnf = toVary >>> rnf++instance Functor (VEither errs) where+ fmap :: forall a b. (a -> b) -> VEither errs a -> VEither errs b+ {-# INLINE fmap #-}+ fmap = mapRight++instance Applicative (VEither errs) where+ {-# INLINE pure #-}+ pure = VRight++ {-# INLINE (<*>) #-}+ (VRight fun) <*> (VRight val) = VRight (fun val)+ (VLeft err) <*> _ = (VLeft err)+ _ <*> (VLeft err) = (VLeft err)++instance Monad (VEither errs) where+ (>>=) :: forall a b. VEither errs a -> (a -> VEither errs b) -> VEither errs b+ (VRight a) >>= fun = fun a+ (VLeft err) >>= _ = (VLeft err)++instance Foldable (VEither errs) where+ foldMap _ (VLeft _) = mempty+ foldMap f (VRight y) = f y++ foldr _ z (VLeft _) = z+ foldr f z (VRight y) = f y z++ length (VLeft _) = 0+ length (VRight _) = 1++instance Traversable (VEither errs) where+ traverse _ (VLeft x) = pure (VLeft x)+ traverse f (VRight y) = VRight <$> f y++instance Semigroup (VEither errs a) where+ (VRight a) <> _ = (VRight a)+ _ <> b = b++-- Look ma! A Hand-written Generic instance!+instance Generic (VEither errs a) where+ type (Rep (VEither errs a)) = D1+ (MetaData "VEither" "Vary.VEither" "vary" False)+ (C1+ (MetaCons "VLeft" PrefixI False)+ (S1+ (MetaSel+ Nothing NoSourceUnpackedness NoSourceStrictness DecidedLazy)+ (Rec0 (Vary errs)))+ :+: C1+ (MetaCons "VRight" PrefixI False)+ (S1+ (MetaSel+ Nothing NoSourceUnpackedness NoSourceStrictness DecidedLazy)+ (Rec0 a)))++ from :: VEither errs a -> Rep (VEither errs a) x+ from ve =+ case ve of+ (VLeft err) -> M1 $ L1 $ M1 $ M1 $ K1 err+ (VRight val) -> M1 $ R1 $ M1 $ M1 $ K1 val++ to :: Rep (VEither errs a) x -> VEither errs a + to rep = case rep of+ (M1 (L1 (M1 (M1 (K1 err))))) -> (VLeft err)+ (M1 (R1 (M1 (M1 (K1 val))))) -> (VRight val)+++-- Conceptually VEither is a Bifunctor,+-- but the kind does not align :-(+-- p has to be Type -> Type -> Type+-- But in the case of VEither it is [Type] -> Type -> Type+--+-- instance Bifunctor VEither where+-- first = mapLeft+-- second = mapRight+-- bimap = veither
+ test/README.lhs view
@@ -0,0 +1,341 @@+# Vary: friendly and fast Variant types for Haskell+[](https://hackage.haskell.org/package/vary)+[](https://hackage.haskell.org/package/vary/docs/Vary.html)+[](https://github.com/Qqwy/haskell-vary/actions/workflows/test.yaml)+++[API Documentation](https://hackage.haskell.org/package/vary/docs/Vary.html)+++Just like tuples are a version of a user-defined product type (only without the field names), a Variant is a version of a user-defined sum type (but without the field names).++Variant types are the generalization of `Either`. Especially in the situation where you want to handle multiple errors, Variant types are a great abstraction to use.++Variant types are sometimes called '_polymorphic_ variants' for disambiguation. They are also commonly known as (open) unions, coproducts or extensible sums.++## General Usage++### Setup++The modules in this library are intended to be used qualified:++```haskell ignore+import Vary (Vary, (:|))+import qualified Vary++import Vary.VEither (VEither(VLeft, VRight))+import qualified Vary.VEither as VEither+```++The library is intended to be used with the following extensions active:++```haskell top:0+{-# LANGUAGE GHC2021 #-} -- Of these, Vary uses: TypeApplications, TypeOperators, FlexibleContexts+{-# LANGUAGE DataKinds #-}+```++### Simple usage++You can construct `Vary` using `Vary.from`:++```haskell+int_or_string :: Bool -> Vary '[Int, String]+int_or_string bool = + if bool then + Vary.from @Int 42+ else+ Vary.from @String "hello world"++-- You can also use the more general type,+-- which allows you to use a function to insert elements into any variant+-- that happens to contain the given type(s), +-- regardless of order or what other types are in its list:+int_or_string2 :: (Int :| l, String :| l) => Bool -> Vary l+int_or_string2 bool = + if bool then + Vary.from @Int 69+ else+ Vary.from @String "I like cheese"++```++You can check whether a particular variant is inside and attempt to extract it using `Vary.into`:++```haskell+maybe_an_int :: Bool -> Maybe Int+maybe_an_int bool = Vary.into @Int (int_or_string bool)+```++And you can match the various possibilities using `Vary.on`, together with `Vary.exhaustiveCase` if you've handled all cases:++```haskell+stringify vary = + vary & + ( Vary.on @String (\string -> "Found a string: " <> show string)+ $ Vary.on @Int (\int -> "Found an int: " <> show int)+ $ Vary.exhaustiveCase+ )+```++It is generally recommended to keep the type generic at the place you construct a `Vary` (using `Vary.from`), and make the type concrete at the place you use it (for instance when using `Vary.on` or `Vary.into`).++This way, the construction functions can be used in any context, regardless of what other possibilities the caller might want to handle. (See also the 'motivation' example below).++In some cases you already have a concrete `Vary` type, and you want to pass it to or return it from a function expecting another shape (different order of elements, having less or more elements). In those cases, `Vary.morph` will help you out :-).++### VEither++You have now seen `Vary`. There is the closely related type `VEither`. Its runtime representation is the same. The difference between `Vary (a : errs)` and `VEither errs a` is that while `Vary` considers all types in its list of possibilities to be equal, `VEither` considers the `a` to be a 'success' (`VRight`), whereas any of the types in `errs` are considered 'errors' (`VLeft`).++This means that `VEither` can implement `Functor`, `Applicative`, `Monad`, `Foldable`, `Traversable`, `Generic` and a few other fun typeclasses, making it really easy to use for error handling.++## Motivation: Why is 'Vary' useful?++ Say we are writing an image thumbnailing service.++ * Given an image URL+ * We attempt to download it.+ * This can fail, because the URL is incorrect;+ * Or the URL /is/ correct but the server could not be reached (in which case we want to re-try later);+ * Or the server /could/ be reached, but downloading took longer than a set time limit.++ * We pass it to a thumbnailing program.+ * This can fail, because the downloaded file might turn out actually not to be a valid image file (PNG or JPG);+ * Or even if the downloaded file /is/ an image, it might have a much too high resolution to attempt to read;+++ The first instinct might be to write dedicated sum types for these errors like so:++```haskell+data Image = Image+ deriving (Eq, Show)++data DownloaderError1+ = IncorrectUrl1+ | ServerUnreachable1+ | DownloadingTimedOut1 + deriving (Eq, Ord, Show)++data ThumbnailError1+ = NotAnImage1+ | TooBigImage1+ deriving (Eq, Ord, Show)++download1 :: String -> Either DownloaderError1 Image+download1 url = + -- Pretend we're doing hard work here+ Right Image++thumbnail1 :: Image -> Either ThumbnailError1 Image+thumbnail1 image = + -- Pretend we're huffing and puffing+ Right Image+```++But if we try to plainly combine these two functions, we get a compiler error:++```haskell+thumbnailService1 url = do+ image <- download1 url+ thumb <- thumbnail1 image+ pure thumb+```++```+error:+ • Couldn't match type ‘ThumbnailError’ with ‘DownloaderError’+ Expected: Either DownloaderError Image+ Actual: Either ThumbnailError Image+```+++We could \'solve\' this problem by adding yet another manual error type:++```haskell+data ThumbnailServiceError1+ = DownloaderError1 DownloaderError1+ | ThumbnailError1 ThumbnailError1+ deriving (Eq, Ord, Show)++thumbnailService2 :: String -> Either ThumbnailServiceError1 Image+thumbnailService2 url = do+ image <- first DownloaderError1 $ download1 url+ thumb <- first ThumbnailError1 $ thumbnail1 image+ pure thumb+```+++This 'works', although already we can see that we're doing a lot of manual ceremony to pass the errors around.++And wait! We wanted to re-try in the case of a `ServerUnreachable` error!++```haskell+waitAndRetry = undefined :: Word -> (() -> a) -> a++thumbnailServiceRetry2 :: String -> Either ThumbnailServiceError1 Image+thumbnailServiceRetry2 url = + case download1 url of+ Left ServerUnreachable1 -> waitAndRetry 10 (\_ -> thumbnailServiceRetry2 url) + Left other -> Left (DownloaderError1 other)+ Right image -> do+ thumb <- first ThumbnailError1 $ thumbnail1 image+ pure thumb+```++We now see:++- Even inside `thumbnailService` there now is quite a bit of ceremony + w.r.t. wrapping,unwrapping and mapping between error types.+- Callers will be asked to pattern match on the @ServerUnreachable@ error case,+ even though that case will already be handled inside the `thumbnailService` function itself!+- Imagine what happens when using this small function in a bigger system with many more errors!+ Do you keep defining more and more wrapper types for various combinations of errors?++#### There is a better way!++With the `Vary` and related `Vary.VEither.VEither` types, you can mix and match individual errors (or other types) at the places they are used.++```haskell top:2+import Vary (Vary, (:|))+import qualified Vary+import Vary.VEither (VEither(..))+import qualified Vary.VEither as VEither+```+```haskell+data IncorrectUrl2 = IncorrectUrl2 deriving (Eq, Ord, Show)+data ServerUnreachable2 = ServerUnreachable2 deriving (Eq, Ord, Show)+data DownloadingTimedOut2 = DownloadingTimedOut2 deriving (Eq, Ord, Show)++data NotAnImage2 = NotAnImage2 deriving (Eq, Ord, Show)+data TooBigImage2 = TooBigImage2 deriving (Eq, Ord, Show)++download :: (ServerUnreachable2 :| err, IncorrectUrl2 :| err) => String -> VEither err Image+download url = + -- Pretend a lot of network communication happens here+ VRight Image++thumbnail :: (NotAnImage2 :| err, TooBigImage2 :| err) => Image -> VEither err Image+thumbnail image = + -- Pretend this is super hard+ VRight Image+```++Here is the version without the retry:++```haskell+thumbnailService :: String -> VEither [ServerUnreachable2, IncorrectUrl2, NotAnImage2, TooBigImage2] Image+thumbnailService url = do+ image <- download url+ thumb <- thumbnail image+ pure thumb+```++And here is all that needed to change to have a retry:++```haskell+thumbnailServiceRetry :: String -> VEither [IncorrectUrl2, NotAnImage2, TooBigImage2] Image+thumbnailServiceRetry url = do+ image <- download url + & VEither.onLeft (\ServerUnreachable2 -> waitAndRetry 10 (\_ -> thumbnailServiceRetry url)) id+ thumb <- thumbnail image+ pure thumb+```++- No more wrapper type definitions!+- Handing an error makes it disappear from the output type!++## Why anoher Variant library?++I am aware of the following Haskell libraries offering support for Variant types already:++- [vinyl](https://hackage.haskell.org/package/vinyl)+- [extensible](https://hackage.haskell.org/package/extensible)+- [freer](https://hackage.haskell.org/package/freer)+- [fastsum](https://hackage.haskell.org/package/fastsum)+- [union](https://hackage.haskell.org/package/union)+- [open-union](https://hackage.haskell.org/package/open-union)+- [world-peace](https://hackage.haskell.org/package/world-peace)+- [haskus-utils-variant](https://hackage.haskell.org/package/haskus-utils-variant)++Vary improves upon them in the following ways:++- Function names in these libraries are long and repetitive, and often seem to be very different from names used elsewhere in `base` or the community.+ - `Vary` is intended to be used `qualified`, making the function names short and snappy, and allowing re-use of names like `map`, `from`, `on` and `into`.+- Many libraries define their variant type using a [Higher Kinded Data](https://reasonablypolymorphic.com/blog/higher-kinded-data/) pattern. This is really flexible, but not easy on the eyes.+ - `Vary`'s type is readable, which is what you want for the common case of using them for error handling.+ - It also means less manual type signatures are needed :-).+- Many libraries (exceptions: `fastsum`, `haskus`) define their variant as a GADT-style linked-list-like datatype. The advantage is that you need no `unsafeCoerce` anywhere. The disadvantage is that this has a huge runtime overhead.+ - `Vary` uses a single (unwrapped, strict) Word for the tag. GHC is able to optimize this representation very well!+ - Conversion between different variant shapes are also constant-time, as only this tag number needs to change.+- With the exception of `world-peace` and `haskus`, documentation of the libraries is very sparse.+ - All of the functions in `Vary` are documented and almost all of them have examples. ([Doctested](https://github.com/martijnbastiaan/doctest-parallel#readme) by the way!)+- The libraries try to make their variant be 'everything it can possibly be' and provide not only functions to work with variants by type, but also by index, popping, pushing, concatenating, handling all cases using a tuple of functions, etc. This makes it hard for a newcomer to understand what to use when.+ - `Vary` intentionally only exposes functions to work _by type_.+ - There is _one_ way to do case analysis of a `Vary`, namely using `Vary.on`. Only one thing to remember!+ - Many shape-modification functions were combined inside `Vary.morph`, so you only ever need that one!+ - Only the most widely-useful functions are provided in `Vary` itself. There are some extra functions in `Vary.Utils` which are intentionally left out of the main module to make it more digestible for new users. +- Libraries are already many years old (with no newer updates), and so they are not using any of the newer GHC extensions or inference improvements.+ - `Vary` makes great use of the `GHC2021` group of extensions, TypeFamilies and the `TypeError` construct to make most type errors disappear and for the few that remain it should be easy to understand how to fix them.++## Acknowledgements++This library stands on the shoulders of giants.++In big part it was inspired by the great Variant abstraction [which exists in PureScript](https://pursuit.purescript.org/packages/purescript-variant/8.0.0) (and related [VEither](https://pursuit.purescript.org/packages/purescript-veither/1.0.5)).++Where PureScript has a leg up over Haskell is in its support of row types. To make the types nice to use in Haskell even lacking row typing support was a puzzle in which the [Effectful](https://github.com/haskell-effectful/effectful) library gave great inspiration (and some type-level trickery was copied essentially verbatim from there.)++Finally, a huge shoutout to the pre-existing Variant libraries in Haskell. Especially to [haskus-utils-variant](https://hackage.haskell.org/package/haskus-utils-variant) and [world-peace](https://hackage.haskell.org/package/world-peace) and the resources found in [this blog post](https://functor.tokyo/blog/2019-07-11-announcing-world-peace) by world-peace's author.++## Is it any good?++Yes ;-)++<!-- +The following is executed by the README test runner,+but we don't want it to be visible to human readers:++```haskell top:1+{-# OPTIONS_GHC -fdefer-type-errors #-} -- We want to show some incorrect examples!+module Main where++import Test.Hspec (hspec, describe, it, shouldBe)+import Test.ShouldNotTypecheck (shouldNotTypecheck)+import Data.Bifunctor (first)+import Data.Function ((&))++```++```haskell+main :: IO ()+main = hspec $ do+ describe "simple examples" $ do+ it "string_or_int" $ do+ int_or_string True `shouldBe` (Vary.from @Int 42)+ int_or_string False `shouldBe` (Vary.from @String "hello world")+ it "maybe_an_int" $ do+ maybe_an_int True `shouldBe` (Just 42)+ maybe_an_int False `shouldBe` Nothing+ it "stringify" $ do+ stringify (int_or_string2 True) `shouldBe` "Found an int: 69"+ stringify (int_or_string2 False) `shouldBe` "Found a string: \"I like cheese\""+++ describe "motivating example" $ do+ describe "bad v1" $ do+ it "should not typecheck" $+ shouldNotTypecheck (thumbnailService1)+ describe "bad v2" $ do+ it "should work (but be verbose)" $+ thumbnailService2 "http://example.com" `shouldBe` (Right Image)+ describe "bad v2 (wth retry)" $ do+ it "should work (but be super verbose)" $+ thumbnailServiceRetry2 "http://example.com" `shouldBe` (Right Image)+ describe "nice" $ do+ it "should work nicely" $+ thumbnailService "http://example.com" `shouldBe` (VRight Image)+ describe "nice (with retry)" $ do+ it "should work nicely" $+ thumbnailServiceRetry "http://example.com" `shouldBe` (VRight Image)+```+-->
+ test/doctest/doctests.hs view
@@ -0,0 +1,10 @@+module Main where++import Test.DocTest (mainFromCabal)+import System.Environment (getArgs)++main :: IO ()+main = do+ args <- getArgs+ let args' = ["--no-implicit-module-import"] <> args+ mainFromCabal "vary" args'
+ test/spec/spec.hs view
@@ -0,0 +1,3 @@+module Main where+main :: IO ()+main = putStrLn "(No separate unit tests yet)"
+ vary.cabal view
@@ -0,0 +1,95 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.35.2.+--+-- see: https://github.com/sol/hpack++name: vary+version: 0.1.0.0+synopsis: Vary: Friendly and fast polymorphic variants (open unions/coproducts/extensible sums)+description: Vary: Friendly and fast Variant types for Haskell+ .+ Just like tuples are a version of a user-defined product type (only without the field names), a Variant is a version of a user-defined sum type (but without the field names).+ .+ Variant types are the generalization of `Either`. Especially in the situation where you want to handle multiple errors, Variant types are a great abstraction to use.+ .+ Variant types are sometimes called '_polymorphic_ variants' for disambiguation. They are also commonly known as (open) unions, coproducts or extensible sums.+ .+ Please see the full README below or on GitHub at <https://github.com/qqwy/haskell-vary#readme>+category: Data, Data Structures, Error Handling+homepage: https://github.com/qqwy/haskell-vary#readme+bug-reports: https://github.com/qqwy/haskell-vary/issues+author: Marten Wijnja (Qqwy)+maintainer: qqwy@gmx.com+copyright: 2024 Marten Wijnja (Qqwy)+license: MIT+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ CHANGELOG.md++source-repository head+ type: git+ location: https://github.com/qqwy/haskell-vary++library+ exposed-modules:+ Vary+ Vary.Utils+ Vary.VEither+ other-modules:+ Vary.Core+ hs-source-dirs:+ src+ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints+ build-depends:+ base >=4.7 && <5+ , deepseq >=1.4.0 && <1.5+ default-language: Haskell2010++test-suite doctests+ type: exitcode-stdio-1.0+ main-is: doctests.hs+ other-modules:+ Paths_vary+ hs-source-dirs:+ test/doctest+ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , deepseq >=1.4.0 && <1.5+ , doctest-parallel+ , vary+ default-language: Haskell2010++test-suite readme+ type: exitcode-stdio-1.0+ main-is: test/README.lhs+ other-modules:+ Paths_vary+ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -pgmL markdown-unlit -Wno-deferred-type-errors -Wno-missing-signatures -Wno-unused-matches -Wno-redundant-constraints -Wno-unused-imports -Wno-missing-export-lists+ build-tool-depends:+ markdown-unlit:markdown-unlit+ build-depends:+ base >=4.7 && <5+ , deepseq >=1.4.0 && <1.5+ , hspec+ , markdown-unlit+ , should-not-typecheck+ , vary+ default-language: Haskell2010++test-suite vary-test+ type: exitcode-stdio-1.0+ main-is: spec.hs+ other-modules:+ Paths_vary+ hs-source-dirs:+ test/spec+ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , deepseq >=1.4.0 && <1.5+ , vary+ default-language: Haskell2010