How to make lenses for records with type-families [duplicate] - haskell

This question already has answers here:
How to derive instances for records with type-families
(2 answers)
Closed 6 years ago.
Here's what I've got, which is not compiling:
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
import Data.Text as T
import Data.Int (Int64)
import Control.Lens
type family Incoming validationResult baseType
type instance Incoming Validated baseType = baseType
type instance Incoming ValidationErrors baseType = Either [T.Text] baseType
data Validated
data ValidationErrors
data Tag = Tag {unTag :: T.Text} deriving (Eq, Show)
data NewTag f = NewTag
{
ntClientId :: Incoming f Int64
, ntTag :: Incoming f Tag
}
$(makeLensesWith abbreviatedFields ''NewTag)
Compilation error:
27 3 error error:
• Illegal type synonym family application in instance:
Incoming f_a1Kvx Int64
• In the instance declaration for
‘HasClientId (NewTag f_a1Kvx) (Incoming f_a1Kvx Int64)’ (intero)
27 3 error error:
• Illegal type synonym family application in instance:
Incoming f_a1Kvx Tag
• In the instance declaration for
‘HasTag (NewTag f_a1Kvx) (Incoming f_a1Kvx Tag)’ (intero)

The problem here is that makeLensesFor will try to generate an instance as follows:
instance HasClientId (NewTag f) (Incoming f Int64) where
....
This, however, is an error because you cannot create an instance for the result of a type family application. To avoid this, we can write the instance manually for each of the two possible choices for f:
-- generate lenses _foo for each record selector foo
-- (in this case, generates _ntClientId and _ntTag lenses)
makeLensesWith (lensRules & lensField .~ mappingNamer (\x -> ['_' : x])) ''NewTag
class HasClientId s a | s -> a where
clientId :: Lens' s a
instance HasClientId (NewTag Validated) Int64 where
clientId = _ntClientId
instance HasClientId (NewTag ValidationErrors) (Either [T.Text] Int64) where
clientId f a = f (ntClientId a) <&> \ntClientId' -> a { ntClientId = ntClientId' }
class HasTag s a | s -> a where
tag :: Lens' s a
instance HasTag (NewTag Validated) Tag where
tag = _ntTag
instance HasTag (NewTag ValidationErrors) (Either [T.Text] Tag) where
tag = _ntTag

Related

How to constrain type family to Show

I'm currently working on an interpreter, which should be able to handle multiple variations of a language.
Therefore I'm designing my AST with type families (a simple example is given below).
How do I tell GHC that my type families need to have Show (and Eq) instances?
I have tried to use StandaloneDeriving, but can't find out how to define the dependency/ constraint.
-- Types.hs
{-# LANGUAGE TypeFamilies #-}
module Types where
data Statement v = CommonStatement (CommonStatement v)
| VariantStatement (VariantStatement v)
deriving (Show)
data CommonStatement v = Skip deriving (Show)
data family VariantStatement v
-- Coroutine.hs
{-# LANGUAGE TypeFamilies #-}
module Coroutine (module Coroutine, module Types) where
import Types
newtype Coroutine = Coroutine [Statement Coroutine]
data instance VariantStatement Coroutine = SomeStatement deriving (Show)
When trying to build this (with stack and resolver lts-16.16), it fails since it can't deduce an instance of Show for (VariantStatement v):
• No instance for (Show (VariantStatement v))
arising from the first field of ‘VariantStatement’
(type ‘VariantStatement v’)
Possible fix:
use a standalone 'deriving instance' declaration,
so you can specify the instance context yourself
• When deriving the instance for (Show (Statement v))
|
7 | deriving (Show)
| ^^^^
This is basically just a matter of chasing down and squashing the errors:
{-# LANGUAGE TypeFamilies, StandaloneDeriving, UndecidableInstances #-}
main= print SomeStatement
data Statement v
= CommonStatement (CommonStatement v)
| VariantStatement (VariantStatement v)
-- StandaloneDeriving and UndecidableInstances
-- are required for this one.
deriving instance Show (VariantStatement v) => Show (Statement v)
data CommonStatement v = Skip deriving (Show)
data family VariantStatement v
newtype Coroutine = Coroutine [Statement Coroutine]
data instance VariantStatement Coroutine = SomeStatement deriving Show

Deriving Show Instance for ADT not working with Higher Kinded Type Families

I was just working through Chris Done's ADT with default example gist available here and ran into a problem: my ADT, with fields defined by higher kinded type families, is not working with a deriving show instance. GHC is telling me I need to derive a Show instance for a Type Family, but I'm not sure how to do. Here's what I have, so far, any comments would be helpful.
In the following example (using ghc 8.8.1), the objective is to define an instance of Show for ShowMe, using derive if possible.
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE ConstraintKinds #-}
data Tag = A | B deriving (Show)
type family TF (p :: Tag) a where
TF 'A a = ()
TF 'B a = a
data ShowMe p = ShowMe
{ a :: !(TF p String)
, b :: String
}
main = connect showMeDefaults { a = "some string" }
where
connect :: ShowMe B -> IO ()
connect _ = pure ()
showMeDefaults :: ShowMe A
showMeDefaults = ShowMe { a = (), b = "asdf" }
-- This works to define Show
{-
instance Show (ShowMe p) where
show _ = "hello"
-}
-- This instance is the line that causes an error
deriving instance Show (ShowMe p)
Subsequently, I'm getting an error that I'm not familiar with from GHC:
show_tf.hs:35:1: error:
• No instance for (Show (TF p String))
arising from a use of ‘showsPrec’
• In the first argument of ‘(.)’, namely ‘(showsPrec 0 b1)’
In the second argument of ‘(.)’, namely
‘((.)
(showsPrec 0 b1)
((.)
GHC.Show.showCommaSpace
((.)
(showString "b = ") ((.) (showsPrec 0 b2) (showString "}")))))’
In the second argument of ‘(.)’, namely
‘((.)
(showString "a = ")
((.)
(showsPrec 0 b1)
((.)
GHC.Show.showCommaSpace
((.)
(showString "b = ") ((.) (showsPrec 0 b2) (showString "}"))))))’
When typechecking the code for ‘showsPrec’
in a derived instance for ‘Show (ShowMe p)’:
To see the code I am typechecking, use -ddump-deriv
|
35 | deriving instance Show (ShowMe p)
If we recompile, using the ghc -ddump-deriv, the following is returned:
[1 of 1] Compiling Main ( show_tf.hs, show_tf.o )
==================== Derived instances ====================
Derived class instances:
instance GHC.Show.Show Main.Tag where
GHC.Show.showsPrec _ Main.A = GHC.Show.showString "A"
GHC.Show.showsPrec _ Main.B = GHC.Show.showString "B"
Derived type family instances:
==================== Filling in method body ====================
GHC.Show.Show [Main.Tag]
GHC.Show.show = GHC.Show.$dmshow #(Main.Tag)
==================== Filling in method body ====================
GHC.Show.Show [Main.Tag]
GHC.Show.showList = GHC.Show.$dmshowList #(Main.Tag)
Linking show_tf ...
Conceptually, I think what I should be able to derive a Show instance for TF, but when I do that, I get the following:
show_tf.hs:36:31: error:
• Illegal type synonym family application ‘TF 'A a’ in instance:
Show (TF 'A a)
• In the stand-alone deriving instance for
‘(Show a) => Show (TF 'A a)’
|
36 | deriving instance (Show a) => Show (TF 'A a)
This error also appears if I just try to define the Show instance myself for TF 'A a. I've searched "Illegal type synonym", and haven't come up up with a way around this.
You need to add
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
and then suggest the wanted context to GHC:
deriving instance Show (TF p String) => Show (ShowMe p)
GHC won't add that context automatically since it can be surprising to the programmer.

How can I implement fromJSON on a GADT with custom type class constraints?

I have the following GADT:
{-# LANGUAGE GADTs #-}
data LogProtocol a where
Message :: String -> LogProtocol String
StartRun :: forall rc. (Show rc, Eq rc, Titled rc, ToJSON rc, FromJSON rc)
=> rc -> LogProtocol rc
... and many more...
toJSON is straight forward and not shown.
fromJSON implementation is based on:
This SO Question and
This Blog Post - pattern 2
and is as follows:
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TemplateHaskell #-}
-- tag type is used in to/ from JSON to reduce the use of magic strings
data LPTag = MessageT |
StartRunT |
... and many more...
deriving (Show, Eq, Enum)
tagList :: Enum a => [a]
tagList = enumFrom $ toEnum 0
$(deriveJSON defaultOptions ''LPTag)
-- a wrapper to hide the a type param in the GADT
data Some (t :: k -> *) where
Some :: t x -> Some t
instance FromJSON (Some LogProtocol) where
parseJSON :: Value -> Parser (Some LogProtocol)
parseJSON v#(Object o) =
let
tag :: Maybe LPTag
tag = do
t <- (HML.lookup "type" o)
parseMaybe parseJSON t
failMessage :: [Char]
failMessage = toS $ "Could not parse LogProtocol no type field or type field value is not a member of specified in: "
<> (show(tagList :: [LPTag]))
<> show v
in
maybe
(fail failMessage )
(
\case
MessageT -> Some <$> (Message <$> o .: "txt")
StartRunT -> Some <$> (StartRun <$> o .: "runConfig")
)
tag
parseJSON wrng = typeMismatch "LogProtocol" wrng
The case for '''Message''' is fine. The problem I am having are errors such as:
* No instance for (Titled x2) arising from a use of `StartRun'
* In the first argument of `(<$>)', namely `StartRun'
In the second argument of `(<$>)', namely
`(StartRun <$> o .: "runConfig")'
In the expression: Some <$> (StartRun <$> o .: "runConfig")
Anywhere I have my own type class constraints (such as Titled)
in the data constructor the compiler says "No".
Is there a way to resolve this?
Existential types are an antipattern, especially if you need to do deserialization. StartRun should contain a concrete type instead. Deserialization requires a concrete type anyway, hence you might as well specialize StartRun to it.

Haskell Constraint Kinds - default constraint for default implementation

Headline: I would like to provide a default implementation for a class method parametrised over a constraint, which uses the default instance for that constraint.
Consider the following:
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeFamilies #-}
import GHC.Exts (Constraint)
class Foo a where
type Ctx a :: Constraint
type Ctx a = Show a
foo :: (Ctx a) => a -> String
foo = show
main :: IO ()
main = putStrLn "Compiles!"
This fails to compile with the error of:
Could not deduce (Show a) arising from a use of ‘show’
from the context (Foo a)
From my perspective, it should be using the default constraint of Show, which would let this compile. Is there any reason this doesn't work, or can anyone suggest a good way to achieve this?
You can achieve this using DefaultSignatures:
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DefaultSignatures #-}
import GHC.Exts (Constraint)
class Foo a where
type Ctx a :: Constraint
type Ctx a = Show a
foo :: (Ctx a) => a -> String
default foo :: Show a => a -> String
foo = show
main :: IO ()
main = putStrLn "Compiles!"
From my perspective, it should be using the default constraint of Show, which would let this compile.
The reason your approach doesn't work is that the user of your class should be able to override any number of defaults. Your code would break if someone tried to override Ctx but not foo.

To what extent can I get Aeson to do the heavy lifting?

I'm trying to avoid writing definitions for toJSON. This is the error I encounter:
Datatypes.hs:92:10:
No instance for (aeson-0.6.0.2:Data.Aeson.Types.Class.GToJSON
(GHC.Generics.Rep (HashMap Key Project)))
arising from a use of `aeson-0.6.0.2:Data.Aeson.Types.Class.$gdmtoJSON'
Possible fix:
add an instance declaration for
(aeson-0.6.0.2:Data.Aeson.Types.Class.GToJSON
(GHC.Generics.Rep (HashMap Key Project)))
In the expression:
(aeson-0.6.0.2:Data.Aeson.Types.Class.$gdmtoJSON)
In an equation for `toJSON':
toJSON = (aeson-0.6.0.2:Data.Aeson.Types.Class.$gdmtoJSON)
In the instance declaration for `ToJSON (HashMap Key Project)'
I get similar errors for all my HashMap data declarations.
Here's the relevant code. Let me know if there is missing information.
{-# LANGUAGE DeriveGeneric #-} -- for JTask and Fields ToJSON instances:w!
{-# LANGUAGE DeriveDataTypeable #-} -- This may be needed for HashMaps
{-# LANGUAGE FlexibleInstances #-} -- for the HashMap ToJSON instances
{-# LANGUAGE DefaultSignatures #-}
import Prelude
import Data.ByteString
import GHC.Generics (Generic )
import Data.Data
import Data.Typeable (Typeable) -- fix HashMap ToJSON instances? maybe
import Data.Aeson
import Data.Aeson.Generic
import Data.Aeson.Types -- (ToJSON,FromJSON)
import Data.HashMap.Strict (HashMap)
data JTask = JTask {fields :: Fields} deriving (Typeable,Data,Generic)
data Fields = Fields { project :: HashMap Key Project
, summary :: ByteString
, issuetype :: HashMap Name Task
, versions :: [HashMap Name Version]
, description :: ByteString
} deriving (Typeable,Data,Generic)
data Key = Key deriving (Typeable,Data,Generic)
instance Show Key where
show Key = "key"
data Name = Name deriving (Typeable,Data,Generic)
instance Show Name where
show Name = "name"
data Task = Task deriving (Typeable,Data,Generic)
type Version = ByteString -- Placeholder type. Probably using Day for realsies.
data Project = BNAP deriving (Typeable,Data,Generic) -- Fill this out as we go
instance Generic (HashMap Key Project)
instance Data (HashMap Key Project)
--instance GToJSON (HashMap Key Project)
instance Generic (HashMap Name ByteString)
instance Data (HashMap Name ByteString)
instance Generic (HashMap Name Task)
instance Data (HashMap Name Task)
-- JSON instances
instance ToJSON CreateError
instance ToJSON Fields
instance ToJSON JTask
instance ToJSON Key
instance ToJSON Name
instance ToJSON Task
instance ToJSON Project
instance ToJSON (HashMap Key Project)
instance ToJSON (HashMap Name Task)
instance ToJSON (HashMap Name ByteString)
-- instance ToJSON Version uncomment when we change Version's type.
I cannot make an instance for Data.Aeson.Types.Class.GToJSON because Data.Aeson.Types.Class
is not exported. What are my options? What will I have to write manually? Is deriveJSON the best choice?
Update:
I implemented the suggestion below.
Here's the code
createObject :: CreateConf -> ResourceT IO Value
createObject (CreateConf iSummary iDesc dd) = do
let jfields = Fields {project = singleton Key BNAP
,summary = iSummary
,issuetype = singleton Name Task
,versions = [singleton Name (calcVersion dd)]
,description = iDesc
}
return $ toJSON (JTask jfields)
The first instance yields Object fromList [("key",Array (fromList []))])
Second instance yields Object fromList [("name",Array (fromList []))]
any idea why name and key are empty?
How could I find out?
Would it be easier to just use deriveJSON.
update:
Thanks to help from NathanHowell the more important problem has been solved, which is the GToJSON instance for unary types. The solution was to make my own instances for the unary types. The JSON objects are out of order, but I don't know if that matters. If it does, it seems another manual ToJSON instance for Fields would fix that.
update:
Okay a little context. I'm writing a JIRA front-end. I mention that because people in the future may come here to discover the following good news: JIRA doesn't care about Object order.
Aeson provides a ToJSON instance for HashMap String a. The easiest way to get things working is to use this instance by converting the HashMap keys to Strings. Just a touch of boilerplate and it uses the Generic instances for everything else.
{-# LANGUAGE DeriveGeneric #-} -- for JTask and Fields ToJSON instances:w!
{-# LANGUAGE FlexibleInstances #-} -- for the HashMap ToJSON instances
{-# LANGUAGE DefaultSignatures #-}
import Prelude
import Data.ByteString
import GHC.Generics (Generic )
import Data.Aeson
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
data JTask = JTask {fields :: Fields} deriving (Generic)
data Fields = Fields { project :: HashMap Key Project
, summary :: ByteString
, issuetype :: HashMap Name Task
, versions :: [HashMap Name Version]
, description :: ByteString
} deriving (Generic)
data Key = Key deriving (Generic)
instance Show Key where
show Key = "key"
data Name = Name deriving (Generic)
instance Show Name where
show Name = "name"
data Task = Task deriving (Generic)
type Version = ByteString -- Placeholder type. Probably using Day for realsies.
data Project = BNAP deriving (Generic) -- Fill this out as we go
instance ToJSON Fields
instance ToJSON JTask
instance ToJSON Key
instance ToJSON Name
instance ToJSON Task
instance ToJSON Project
mapfst :: (a -> b) -> [(a, v)] -> [(b, v)]
mapfst f = fmap $ \ (k, v) -> (f k, v)
instance ToJSON a => ToJSON (HashMap Key a) where
toJSON = toJSON . HashMap.fromList . mapfst show . HashMap.toList
instance ToJSON a => ToJSON (HashMap Name a) where
toJSON = toJSON . HashMap.fromList . mapfst show . HashMap.toList

Resources