I have a data type defined in another library. I would like to hook into that datatype with a lens generated by the Control.Lens library.
Do I need to newtype my type in my code or is it considered safe to lens an already defined data type?
You don't need a newtype. There are actually many packages on hackage that define lenses for already existing types (for example, xml-lens or even lens itself).
The problem with defining instances is that there is no way to hide them. If you define lenses, you can just hide them when importing, like any other function:
import Module.Lens hiding (someGeneratedLens, ...)
This is not possible with instances (See https://stackoverflow.com/a/8731340/2494803 for reasons). Lenses are also not required to be globally unique, unlike instances.
Related
The following introduction is provided to ensure you understand how I reached the problem (to not fall prey to the XY problem):
I am working on a program which turns a parser in Parsec-like DSL into an actual LL(1) parser (and in the future similarly for LALR(1) or others).
It basically works as follows:
The DSL consists of functions which together build up a GADT. The result is a datatype which might contain cycles ('tying the knot'-style).
Data.Reify is used to turn this into a graph representation ('untying' the knot).
Perform the necessary transformations on this graph to turn it into a LL(1) parsing table.
Construct the parser which will use this table.
Because we want to be able to use the data that is recognized while parsing to construct some kind of result, we need to pass on functions through steps (1.) to (4.).
In steps 1, 2, 3 we can get away with using an existential datatype. It's only when actually running the parser, that I found myself requiring Data.Dynamic and its dynApp to combine the results. We know that the types line up (since in step (1) the GADT construction is type-checked), but I did not figure out how to use the existential types in any other way (as each of the parsing steps might have a very different type).
The current procedure thus 'works' but requires Dynamic. Also, the whole parser, while based on a written function definition, will be constructed at runtime.
Enter Template Haskell: Since the parser function is defined in a different module, it ought to be able to construct the parser at compile-time.
However, there is no Lift instance for Dynamic!
Furthermore, attempting to directly lift the existential types (i.e. require a Lift constraint on them) instead also does not work, as these are almost always functions!
How can we lift a GADT containing either Dynamics or Typeable a => a's into a TemplateHaskell quotation?
Or is there another approach to be able to handle this situation?
I'm not familiar with GHC internals but I have a couple questions about ConstraintKinds.
It says from GHC.Exts that
data Constraint :: BOX
which is misleading because Constraint is a kind of sort BOX. This brings us to the first question: we can import and export kinds? How does that work?
Please correct me on this next part if I'm totally off. From trying out different imports and glancing around at the source on hackage, my guess is that GHC.Exts imports Constraint from GHC.Base, who in turn, imports it from GHC.Prim. But I do not see where it is defined in GHC.Prim?
To my knowledge, there is no definition of Constraint in any Haskell source file. It's a built-in, wired-in name that is defined to belong within GHC.Prim in the GHC sources itself. So in particular Constraint is not a promoted datatype, there's no corresponding datatype of kind * that is called Constraint.
There are other kinds in GHC that are treated similarly, such as AnyK, OpenKind or even BOX itself.
GHC doesn't really make a big difference internally between datatypes and kinds and anything above. That's why they e.g. all show up as being defined using data albeit with different target kinds.
Note that as far as GHC is concerned, we also have
data BOX :: BOX
It's impossible for a user to directly define new "kinds" of super-kind BOX, though.
As far as I know, importing / exporting also makes no difference between the type and kind namespaces. So e.g.
import GHC.Exts (OpenKind, BOX, Constraint)
is legal. In fact, if you then say
x :: Constraint
x = undefined
you don't get a scope error, but a kind error, saying that a type of kind * is expected, but a type/kind of kind BOX is provided.
I should perhaps also say that the whole story about kinds is somewhat in flux, and there are proposals being discussed that change this a bit: see e.g. https://ghc.haskell.org/trac/ghc/wiki/NoSubKinds for related discussion.
Is there a Data.Binary instance for Data.Time.Calendar.Day?
More generally, what is one supposed to do if Data.Binary have not been provided for a particular datatype in a widely used library?
If you just create a Binary instance and place it in your module then you'll be creating an orphan instance which can cause a lot of confusion later when someone imports your module---it'll drag along that orphan instance and possibly conflict with their understanding of how dates should be made Binary.
If you have a very canonical instance, try pushing it to the library author. It's easy to add the instance if it's a good idea and it can benefit anyone who uses that library.
If that isn't an option (or if you have a non-canonical instance) then you probably want to create a newtype wrapper. They're "free" in that the compiler deletes them automatically, but they allow a type to take on an entirely new identity with new typeclass instances.
I've done this before to handle particular parses of, for instance, "dates in this format" compared to Date broadly.
Apparently it's a bad idea to put a typeclass constraint on a data declaration [src], [src].
I haven't personally come across a desire to constrain the types within data types I've created, but it's not obvious to me why the language designers "decided it was a bad idea to allow". Why is that?
I haven't personally come across a desire to constrain the types within data types I've created, but it's not obvious to me why the language designers "decided it was a bad idea to allow". Why is that?
Because it was misleading and worked completely backwards from what would actually be useful.
In particular, it didn't actually constrain the types within the data type in the way you're probably expecting. What it did do was put a class constraint on the data constructor itself, which meant that you needed to satisfy the instance when constructing a value... but that was all.
So, for instance, you couldn't simply define a binary search tree with an Ord constraint and then know that any tree has sortable elements; the lookup and insert functions would still need an Ord constraint themselves. All you'd prevent would be constructing an empty tree that "contains" values of some non-ordered type. As far as pattern matching was concerned, there was no constraint on the contained type at all.
On the other hand, the people working on Haskell didn't think that the sensible version (that people tended to assume data type contexts provided) was a bad idea at all! In fact, class constraints on a data type declared with GADT syntax (generalized algebraic data types, enabled in GHC with the GADTs language pragma) do work in the obvious way--you need a constraint to construct the value, and the instance in question also gets stored in the GADT, so that you don't need a constraint to work with values, and pattern matching on the GADT constructor lets you use the instance it captured.
It's not actually a bad idea to add a typeclass constraint on a
data type - it can be very useful, and doesn't break your other code.
The badness is all about the fact that often people expect that they can then
use the data type to excuse them from putting a constraint on functions
that use the data type, but that's not the case.
(You could argue that an implicit constraint can cause problems.)
Putting a constraint on a datatype actually puts it on the all the constructors
that mention the constrained type.
Just as with an ordinary function with a constraint, if you use the constructor,
you must add the constraint. I think that's healthy and above board.
It does ensure you can't put data in your data type unless you can do
certain things with it. Its useful. You won't be creating a programming
faux pas by using one, and it's not bad practice, it's just not as lovely as
they wanted.
The "bad idea to allow" is probably because GADTs is really what they would like.
If GADTs had been around first, they wouldn't have done this.
I don't think it's such a bad thing to have both. If you want a state
manipulating function, you can use a permanently explicit parameter you pass around,
or you can use a monad and make it implicit. If you want a constraint on
data you can use a permanently explicit one on a data declaration or an implicit one
with a GADT. GADTs and monads are more sophisticated, but it doesn't make
explicit parameters or data type constraints wrong.
The general question is which module structure is more convenient when adding instances for existing objects? Which pros and cons there are?
Let's say I want to add NFData instance for Seq type. I can place it in:
Data.Sequence.Extra (as the same thing is done in the vty package)
Data.Sequence.Instances.NFData (more precise)
Control.DeepSeq.Instances
Control.DeepSeq.Instances.Sequence
It's the case when I don't own neither the type class nor the data type. The other common situation is when I own a type type class and want to add instances for data type(s) from some "heavy" package from Hackage, like OpenGL. Let's say the type class I designed is very light and has no direct relation to OpenGL. I don't want my type class depend on "heavy" package, so I want to place OpenGL instances in a separate module (it's my intuitive feeling, if you have other opinion, let's discuss it). So, what this module should be:
MyClass.Instances.OpenGL
Graphics.Rendering.OpenGL.Extra (together with instances for other classes)
Graphics.Rendering.OpenGL.Instances.MyClass
What is more flexible solution? At some point OpenGL can be replaced with other library, or MyClass can be replaced too. Are there any subtle nuances?
Also, which scheme is better if choose MyClass.Instances variant:
MyClass.Class module for the class itself and basic instances and MyClass module reexports it (and maybe MyClass.Instances)
MyClass module for the class and basic instances, and MyClass.All reexports everything
MyClass module for the class and basic instances and no module for reexporting.
The usual process of deciding where to put an instance goes something like this:
If you're making a new data type and want an instance for an existing class:
Put the instance in the same module as the data type.
If you're making a new type class and want to make instances for existing types.
Put the instances in the same module as the type class.
Both the type class and the data type already exist (your case).
If the instance is sufficiently general, I'd contact the author of either the type class or the data type and try to convince them to add the instance in their package.
Otherwise, create a newtype wrapper and write an instance for that.
Only as a last resort consider making an orphan instance.