-
-
Save nlinker/1f412befed717fc70067d5c1121d4cc8 to your computer and use it in GitHub Desktop.
I figured out a nice way to pluck exceptions out of a constraint!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE ConstraintKinds #-} | |
{-# LANGUAGE DeriveGeneric #-} | |
{-# LANGUAGE FlexibleContexts #-} | |
{-# LANGUAGE FlexibleInstances #-} | |
{-# LANGUAGE MultiParamTypeClasses #-} | |
{-# LANGUAGE PartialTypeSignatures #-} | |
{-# LANGUAGE TemplateHaskell #-} | |
{-# LANGUAGE TypeApplications #-} | |
{-# LANGUAGE TypeFamilies #-} | |
-- | Polymorphic pluckable exceptions with generic-lens and prisms! | |
-- | |
-- I haven't ever seen an example of someone *shrinking* the constraints in | |
-- an Either. We can do this with the AsType generic prism class. | |
module Main where | |
import Control.Lens | |
import Data.Generics.Sum | |
import Data.Generics.Sum.Typed | |
import Data.Typeable | |
import GHC.Generics (Generic) | |
-- | This is our first exception type. | |
data FooErr = FooErr Int | |
deriving (Show, Generic) | |
-- | And here's our second. | |
data BarErr = BarErr String | |
deriving (Generic, Show) | |
-- | We "throw an exception" and project it into some larger sum, using | |
-- @'AsType' 'FooErr' e@. This uses generics under the hood. | |
foo :: AsType FooErr e => Either e Int | |
foo = Left (review (_Typed @FooErr) (FooErr 3)) | |
-- | 'bar' has the same deal as 'foo' -- we're just throwing an error. | |
bar :: AsType BarErr e => Either e Int | |
bar = Left (review (_Typed @BarErr) (BarErr "hello")) | |
-- | Here, we're collecting both of those constraints. GHC will infer this | |
-- signature just fine, but it needs @NoMonomorphismRestriction@ of course. | |
foobar :: (AsType BarErr e, AsType FooErr e) => Either e Int | |
foobar = bar *> foo *> bar | |
-- | And here's the magic. There are instances of relevant type classes for | |
-- 'Either', and we can "pluck" the FooErr constraint off. So 'e' there | |
-- only has a constraint for @'AsType' 'BarErr' e@. There's also, uh, a lot | |
-- of nasty constraints that are hidden in the generic-lens library, but | |
-- they're mostly about guaranteeing that 'FooErr' does not occur in @e@. | |
plucked :: _ => Either (Either FooErr e) Int | |
plucked = foobar | |
-- | And this works! | |
main :: IO () | |
main = do | |
print (plucked :: Either (Either FooErr BarErr) Int) | |
-- We can also just use case and pluck a single exception off. Note | |
-- that we're casing on foobar, which has the AsType. And Either just | |
-- works. We pattern match on the type and it *just works*! | |
case foobar of | |
Left err -> | |
case err of | |
Left (FooErr i) -> do | |
putStrLn "Got FooErr" | |
print i | |
Right other -> do | |
putStrLn "Got something else" | |
print (typeOf other) | |
Right i -> do | |
putStrLn "Got right" | |
print i |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment