vary 0.1.0.0 → 0.1.0.1
raw patch · 2 files changed
+9/−127 lines, 2 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- src/Vary.hs +7/−125
- vary.cabal +2/−2
src/Vary.hs view
@@ -70,10 +70,14 @@ -- -- >>> import Vary (Vary, (:|)) -- >>> import qualified Vary+--+-- You probably often want to use it together with the "Vary.Either" module:+-- >>> import Vary.Either (VEither(VLeft, VRight))+-- >>> import qualified Vary.VEither as VEither -- --- And for many functions, it is useful or outright necessary to enable the following extensions:+-- And for many functions, it is useful (and sometimes outright necessary) to enable the following extensions: ----- >>> :set -XGHC2021+-- >>> :set -XGHC2021 -- Of these, Vary uses: TypeApplications, TypeOperators, FlexibleContexts -- >>> :set -XDataKinds -- -- Finally, some example snippets in this module make use of 'Data.Function.&', the left-to-right function application operator.@@ -84,129 +88,7 @@ == 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-:}+A longer example on why you would want to use Vary [can be found in the package README on GitHub](https://github.com/qqwy/haskell-vary#readme) -}
vary.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.2.+-- This file has been generated from package.yaml by hpack version 0.36.0. -- -- see: https://github.com/sol/hpack name: vary-version: 0.1.0.0+version: 0.1.0.1 synopsis: Vary: Friendly and fast polymorphic variants (open unions/coproducts/extensible sums) description: Vary: Friendly and fast Variant types for Haskell .