Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I represent data with optional fields at the type-level?

Tags:

haskell

I'm working on a data for control flow that has a value (polymorphic, can be whatever), and it also could have a validator function that checks if the value is still good, and could have a function that "refreshes the value" (returns a new data with a new value).

In vanilla Haskell it can look like this:

data MyData a = MyData
  {value :: a
  ,validator :: Maybe (a -> Bool)
  ,refresher :: Maybe (MyData a -> MyData a)}

What I actually want are these types:

data Refreshable = Refreshable | NotRefreshable
data Validatable = Validatable | NotValidatable
MyData (r :: Refreshable) (v :: Validatable)

I've done just that but only with Refreshable. I want to do it with Validatable too but I'm having a problem with constructors. Just for Refreshable I need to have two constructors, one for refreshable data and another for non-refreshable data. With validatable, I'll need to have 4 constructors! (for refreshable and validatable, for non-refreshable and validatable, for validatable and non-refreshable, and for non-refreshable and non-validatable). And imagine if I'll need another optional field later. Even worse: almost all the fields are the same except for the ones that are changing so there's so much duplication.


I've also tried to amend the situation with typeclasses / type-families.
For example, MyData 'Refreshable 'NotValidatable just becomes Refreshable data => data and I could instance MyData or just remove it for more specific-data that can be an instance.

This is also problematic, because they're not really fields anymore; i.e. I can't take a data without a validator and change it to the same data with a validator (not at the type-level).


This is probably an XY problem; I think a cleaner approach would be to make data types like Refreshable a and Validatable a and compose them in MyData but I don't know how to do that. I can't wrap them because order would change everything.

Is there a clean way to do this? or should I just stick with the 4 constructors? or maybe Haskell isn't ready for this type of things yet? (no pun intended :P).

like image 673
MasterMastic Avatar asked Feb 06 '23 14:02

MasterMastic


1 Answers

Would something like this satisfy your requirements?

import Control.Applicative (Const)
import Data.Functor.Identity

data MyData kv kr a = MyData
  {value :: a
  ,validator :: kv (a -> Bool)
  ,refresher :: kr (MyData a -> MyData a)}

-- examples
type FullData a      = Data Identity   Identity   a
type EmptyData a     = Data (Const ()) (Const ()) a
type ValidableData a = Data Identity   (Const ()) a

Some wrapping/unwrapping required (for Identity).

It is possible to define mnemonic aliases type Present = Identity and type Missing = Const (), with a few extensions on.


Alternatively,

data MyData (v :: Opt) (r :: Opt) a = MyData
  {value :: a
  ,validator :: Validator v a
  ,refresher :: Refresher r a}

data Opt = Yes | No

type family Validator (o :: Opt) a where
    Validator Yes = (a -> Bool)
    Validator No  = ()
-- etc.

-- examples
type FullData a      = Data Yes Yes a
type EmptyData a     = Data No  No  a
type ValidableData a = Data Yes No  a
like image 128
chi Avatar answered Feb 16 '23 03:02

chi