Created
August 7, 2017 22:24
-
-
Save maxigit/506d2ce4a9d07c1aef59c59ab8902dcd to your computer and use it in GitHub Desktop.
Metamorphosis example
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
This is literate haskell file, so let start with the boring bits | |
>{-# LANGUAGE TemplateHaskell #-} | |
>{-# LANGUAGE DuplicateRecordFields #-} | |
>{-# LANGUAGE StandaloneDeriving,FlexibleInstances, FlexibleContexts #-} | |
Metamorphosis is meant to be imported unqualified, and uses lenses to configurate default records. | |
I'll use micro lens. | |
>import Metamorphosis | |
>import Lens.Micro | |
>import Metamorphosis.Applicative | |
>import Data.Functor.Identity | |
Rewritten using Metamorphosis, your example will look like this. | |
First, I found it easir to separate the real type (the valid one which will be used after validation, | |
from the temporary types needs for the validation). | |
The main type will be plain User, without type family or anything. | |
>data User = User | |
> { name :: String | |
> , age :: Int | |
> , active :: Bool | |
> } deriving Show | |
We need now a User parametrized by a functor | |
This is done in metamorphosis b, replacing `User` per `UserF` : `(fdTConsName .~ "UserF")` | |
and the type `t` of a field, by `f t` : `(fdTypes %~ ("f":)` | |
We also want to generate an applicative converter. `const (Just applicativeBCR)` | |
>$(metamorphosis | |
> ( (:[]) | |
> . (fdTConsName .~ "UserF") | |
> . (fdTypes %~ ("f":)) | |
> ) | |
> [''User] | |
> (const (Just applicativeBCR)) | |
> (const []) | |
> ) | |
This will generate | |
data UserF f = User | |
{ name :: f String | |
, age :: f Int | |
, active :: f Bool | |
} | |
but also a convert User to UserF and User to UserF. | |
Generated converters are not direct converter have their result in a applicative functor. | |
UserToUserF :: User -> g (UserF f) | |
UserToUserF (User a b c) = UserF <$> convertA a <*> convertA b <*> convertA c | |
And | |
UserFToUser :: User -> g (UserF f) | |
UserFToUser (User a b c) = UserF <$> convertA a <*> convertA b <*> convertA c | |
In order to be able to see the a UserF we need to deriving a few show instance | |
>deriving instance Show (UserF Maybe) | |
>deriving instance Show (UserF []) | |
>deriving instance Show a => Show (UserF (Either a)) | |
For example, we know we can "produce" a `UserF Maybe` from a `User` so the convertion | |
can be done in the Identity functor | |
-- runIdentity $ aUserToUserF (User "bilbo" 127 True) :: UserF Maybe | |
-- >>> UserF {name = Just "bilbo", age = Just 127, active = Just True} | |
However, we (only) may get a User from a User Maybe, so we can `aUserFToUser` To get a `Maybe User` | |
-- aUserFToUser (UserF (Just "a") (Just 127) (Just False)) :: Maybe User | |
-- >>> Just (User {name = "a", age = 127, active = False}) | |
-- aUserFToUser (UserF (Just "a") Nothing (Just False)) :: Maybe User | |
-- >>> Nothing | |
Now, we are just missing on thing, being able to zip to `UserF` so we can apply the equivalent of a User V to a user V | |
At the moment the validation function has to be done manually | |
>validate :: User -> UserF (Either String) | |
>validate (User a b c) = UserF (Right a) (positive b) (Right c) where | |
> positive x | x <=0 = Left "negative value" | |
> | otherwise = Right x | |
-- validate $ User "a" 3 True | |
-- >>> UserF {name = Right "a", age = Right 3, active = Right True} | |
-- validate $ User "a" 0 True | |
-- >>> UserF {name = Right "a", age = Left "negative value", active = Right True} | |
Now, we can convert back (or not) to a User | |
fullValidate :: User -> Either (UserF (Either String)) User | |
>fullValidate u = let | |
> v = validate u | |
> v' = aUserFToUser v :: Either String User | |
> in case v' of | |
> Left _ -> Left v | |
> Right _ -> Right u | |
-- fullValidate (User "b" 3 False) | |
-- >>> Right (User {name = "b", age = 3, active = False}) | |
-- fullValidate (User "b" (-3) False) | |
-- >>> Left (UserF {name = Right "b", age = Left "negative value", active = Right False}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment