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).
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With