user2882307
user2882307

Reputation:

Require higher kinded type in class instances

I have a type which looks something like this.

data State = Unsanitized | Sanitized

type family BarTy (s :: State) :: Type where
  BarTy 'Unsanitized = String
  BarTy 'Sanitized = Int

data Foo (a :: State) = Foo (Bar a)
data Bar (a :: State) = Bar (BarTy a)

I use the type parameter to be able to change types of certain fields. This is useful for instance when evolving a data structure through a validation pipeline.

Now I want to write a class that can extract something out of this data structure but it doesn't matter which State it is. Full example:

data State = Unsanitized | Sanitized

type family BarTy (s :: State) :: Type where
  BarTy 'Unsanitized = String
  BarTy 'Sanitized = Int

data Foo (a :: State) = Foo (Bar a)
data Bar (a :: State) = Bar (BarTy a)

class Extractor a where
  extract :: a -> (BarTy b)

instance Extractor (Foo a) where
  extract (Foo x) = extract x

instance Extractor (Bar a) where
  extract (Bar x) = x

This obviously doesn't work since the b type variable is always unknown in the instances and thus a type error occurs. I can make another type family that extracts the State type from a given type but this is really clunky for larger types.

data State = Unsanitized | Sanitized

type family BarTy (s :: State) :: Type where
  BarTy 'Unsanitized = String
  BarTy 'Sanitized = Int

type family ExtractState (t :: Type) :: State where
  ExtractState (Foo s) = s
  ExtractState (Bar s) = s
  ExtractState _ = TypeError ('Text "ExtractState: Passed type is not valid.")

data Foo (a :: State) = Foo (Bar a)
data Bar (a :: State) = Bar (BarTy a)

class Extractor a where
  extract :: a -> (BarTy (ExtractState a))

instance Extractor (Foo a) where
  extract (Foo x) = extract x

instance Extractor (Bar a) where
  extract (Bar x) = x

bar1 :: Bar 'Unsanitized
bar1 = Bar "Test"
foo1 = Foo bar1

bar2 :: Bar 'Sanitized
bar2 = Bar 1337
foo2 = Foo bar2

a = extract bar1
b = extract foo1
c = extract bar2
d = extract foo2

What I would ideally like to do is pattern match in the class declaration but I don't know how to do this or if it is even possible:

data State = Unsanitized | Sanitized

type family BarTy (s :: State) :: Type where
  BarTy 'Unsanitized = String
  BarTy 'Sanitized = Int

data Foo (a :: State) = Foo (Bar a)
data Bar (a :: State) = Bar (BarTy a)

class Extractor (t a) where
  extract :: (t a) -> (BarTy a)

instance Extractor (Foo a) where
  extract (Foo x) = extract x

instance Extractor (Bar a) where
  extract (Bar x) = x

Can Haskell do something like this? Or is there another way to achieve what I want?

Any help is appreciated.

Upvotes: 2

Views: 68

Answers (1)

bradrn
bradrn

Reputation: 8477

Remember that you can partially apply types when defining a typeclass:

class Extractor t where
    extract :: t a -> BarTy a

(The same technique is used in the definitions for Functor, Applicative, Monad and in general just about any typeclass meant for use with higher-kinded types.)

Upvotes: 5

Related Questions