i-am-tom / higgledy Goto Github PK
View Code? Open in Web Editor NEWHigher-kinded data via generics
License: MIT License
Higher-kinded data via generics
License: MIT License
I've watched the higgledy from scratch videos, which were fantastic. The first thing that jumped out at me is that in memory representation of HKD Foo is going to be quite in efficient if one would like to do anything other than immediately convert back to a Foo; lots of pointer chasing to get to each field, and the more fields a structure has the worse this would get. I wondered how difficult having HKD(_)
internally produce something similar to SuperRecord's FromNative version of Foo, where accessing each field would become a constant time operation, so performance should ideally approach the hand written HKD version of Foo.
I would personally simplify things a little over what SuperRecord does where it sorts the fields, this seems unnecessary.
I'm mostly opening this issue for discussion of the pros and cons, I'm not tied to the exact choice of library (SuperRecord is the only one I have experience with of the anonymous record libraries, and it has excellent performance).
Currently build
is not great to use – you can mix up the field order:
>>> :{
test :: _
test = build @User
:}
...
... • Found type wildcard ‘_’
... standing for ‘f [Char] -> f Int -> f Bool -> HKD User f’
...
(Here you can't, but if there were two Ints, you could.)
I propose adding a version that uses the named
library:
test
:: "name" :! f [Char]
-> "age" :! f Int
-> "likesDogs" :! f Bool
-> HKD User f
test = buildNamed @User
Then the function can be called like this:
test (#name ...) (#age ...) (#likesDogs ...)
Or in this style, which allows supplying the arguments in any order:
test
! #likesDogs ...
! #name ...
! #age ...
Say I have a type
data User = User
{ name :: Last String
, age :: Int
}
Would you be open to having a version of HKD
say HKD'
which wouldn't wrap name
in f but wraps the remaining fields.
type PartialUser = HKD' User Last
I could take a stab at an implementation of a PR to this effect if it's agreeable to you.
From the base-4.12.0 docs...
This type will be marked deprecated in GHC 8.8, and removed in GHC 8.10. Users are advised to use the variant from Data.Semigroup and wrap it in Maybe.
So for example
>>> import Data.Semigroup (Last (..))
>>> type Partial a = HKD a (Maybe (Last a))
Dank library tho my man.
Consider following declarations
-- create a datatype using barbie's "native" methods
data X f = X { getX :: f Int } deriving (Generic)
instance FunctorB X
instance TraversableB X
instance ProductB X
instance ConstraintsB X
idX :: X Identity
idX = X (Identity 10)
-- and derive isomorphic datatype using higglegy
data Y = Y { getY :: Int } deriving (Generic)
idY :: HKD Y Identity
idY = deconstruct @ Identity (Y 10)
In both cases we can work with bmap
:
> :t bmap (\_ -> Const ()) idX
bmap (\_ -> Const ()) idX :: X (Const ())
> :t bmap (\_ -> Const ()) idY
bmap (\_ -> Const ()) idY :: HKD Y (Const ())
But only "native" version works with bmapC
:
> :t bmapC @ Num (fmap (+1)) idX
bmapC @ Num (fmap (+1)) idX :: X Identity
> :t bmapC @ Num (fmap (+1)) idY
<interactive>:1:1: error:
• Could not deduce: c0 Int arising from a use of ‘bmapC’
• In the expression: bmapC @Num (fmap (+ 1)) idY
most recent version of everything, ghc 8.6.5 from stack's lts-13.23
Hey, Tom! Thanks for the lib, I really like the idea :)
While trying to write an utility using higgledy
and ran into the fact that I can't really derive instances easily for a newtype that wraps an HKD a f
. Taking Show
as an example:
data Wrapper a = Wrapped a | Unwrapped
deriving (Generic, Eq, Show)
newtype Foo a = Foo (HKD a Wrapper)
deriving (Generic)
deriving newtype (Show)
If I try to derive Show
for the type Foo
like above I get the following error:
• Could not deduce (Data.Generic.HKD.Types.GShow 'True (GHKD_ Wrapper (Rep a)))
Have you come across this limitation before? How have you solved it?
The issue for Show
seems to be that higgledy
doesn't export the Data.Generic.HKD.Types.GShow
class so it is not possible to add it to the constraints of a standalone instance declaration like so:
deriving instance
( Data.Generic.HKD.Types.GShow 'True (GHKD_ Update (Rep a)),
Generic a
) =>
Show (Changeset a)
Could GShow
be exported? It seems to be standard practice (see GFromJSON for example).
However, the above is just a band-aid, since other instances cannot be derived either.
I wonder if there is a generalized way to do newtype deriving for instances such as Show
, Eq
, Semigroup
and Monoid
?
As documentation explains you can create something out of nothing using mempty
, which imposes a Monoid
constraint on every field which rules out a whole bunch of useful types.
An alternative would be to impose constraint on functor - Alternative f
can provide "something" filled with "nothing"
data Foo = Foo Int deriving Generic
nofoo :: Alternative f => HKD Foo f
nofoo = bmap (const empty) (deconstruct (Foo undefined) :: HKD Foo (Const ()))
nofoo
can be for example Foo Nothing
or Foo (Left "")
this is not so much an issue, but a failure to understand something on my behalf which might warrant additional documentation.
I came to higgledy from barbies and barbies-th.
There I can basically do
data Person = Person
{ name :: !String,
firstName :: !String,
age :: !Integer
}
deriving (Generic, Show, Eq)
type PersonF f = HKD Person f
p :: PersonF (Const String)
p = ??? (Const "c1") (Const "c2") (Const "c3")
the part with ??? is something I know how to do with barbies, but not with higgledy. the deconstruct
function does not help me as it would construct Const "".
The Barbies documentation mentions a way to change the "outfits" of a barbie using bmap
:
bmap (either (const Nothing) Just) (p :: Person (Either e))
If I try to write a function that uses bmap
on an HKD
type to achieve a similar effect, I get the following error:
data Wrapper a = Wrapped a | Unwrapped deriving (Generic)
f :: HKD a Wrapper -> HKD a Maybe
f x = bmap g x
where
g :: Wrapper a -> Maybe a
g (Wrapped a) = Just a
g Unwrapped = Nothing
Error:
• Could not deduce (Data.Generic.HKD.Types.GFunctorB (Rep a)) arising from a use of ‘bmap’
The higgledy
documentation mentions:
By combining this with some of the Barbies interface (the entirety of which is available to any HKD-wrapped type) [..]
I assumed using bmap
would then just work. Am I doing something wrong? Is it possible to write such a transformation using higgledy
?
The haddock for Construct
is:
class Construct (f :: Type -> Type) (structure :: Type) where
construct :: HKD structure f -> f structure
deconstruct :: structure -> HKD structure f
and
(Functor f, Generic structure, GConstruct f (Rep structure)) => Construct f structureSource
this is pretty amazing and sounds like you can deconstruct
for any f
--- though it seems like it such a thing would require pure
. Indeed the implementation has an Applicative f
constraint, but this wigged me out for a few minutes!
why is there a book in the readme I don't understand why this is the most appropriate emoji thnx
There should be brackets inserted around each field when it is printed using Show
.
*Main Data.Functor.Identity> deconstruct @Identity (User "abc" 5)
User Identity "abc" Identity 5
I tried to drop all the implementations of the barbie classes in favour of the shiny new generic rep, but it didn't work immediately. This issue is here as a reminder that there are at least 50 LoC we could drop if I could fix whatever scary error is emerging when I try.
Fails with:
src/Data/Generic/HKD/Named.hs:31:1: error:
Could not load module ‘Data.GenericLens.Internal’
It is a member of the hidden package ‘generic-lens-core-2.0.0.0’.
Perhaps you need to add ‘generic-lens-core’ to the build-depends in your .cabal file.
It is a member of the hidden package ‘generic-lens-1.1.0.0’.
Perhaps you need to add ‘generic-lens’ to the build-depends in your .cabal file.
Use -v (or `:set -v` in ghci) to see a list of the files searched for.
|
31 | import Data.GenericLens.Internal (GUpcast (..))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
when including generic-lens-core
it fails with:
src/Data/Generic/HKD/Named.hs:31:35: error:
Module ‘Data.GenericLens.Internal’ does not export ‘GUpcast(..)’
|
31 | import Data.GenericLens.Internal (GUpcast (..))
|
It seems GUpcast
has moved to https://hackage.haskell.org/package/generic-lens-core-2.0.0.0/docs/Data-Generics-Product-Internal-Subtype.html , however, it is no longer an exported module. When exporting it it builds.
I also had to import HList
from Data.Generics.Product.Internal.HList
.
I'll put up a PR with these changes, but I guess generic-lens would need to be updated to expose GUpcast
again?
I keep doing strange things. Consider something like this:
data Foo = Foo { int :: Int, bool :: Bool } deriving (Generic, ...)
-- uses Read instances to populate corresponding fields
fromPairs :: [(String, String)] -> HKD Foo Maybe
fromPairs [] == Foo Nothing Nothing
fromPairs [("int", "10")] = Foo (Just 10) Nothing
fromPairs [("bool", "False)] = Foo Nothing (Just False)
Implementation uses label
internally and since it requires HKD structure f
as input I'm using it as label (mempty :: HKD Foo (Const ()))
. Now, any attempt to make it for any structure fails since required constraint (Monoid tuple, Generic xs, Tuple f xs tuple) => Monoid (HKD xs f)
can't be expressed without Tuple
class that's not exported.
Something-something internals something (if they could be exposed).
https://stackoverflow.com/questions/9190638/how-why-and-when-to-use-the-internal-modules-pattern
In https://github.com/lunaris/generic-validated/blob/master/src/Validated/Codes.hs#L35 I end up needing the ProductBC
and GAllB
classes/constraint families. I may be doing it wrong, but I think if one wants to write any polymorphic function that uses some of the barbies
stuff these are necessary. Not sure if they are clean enough to export, or perhaps some class synonym like class (ProductBC ..., GAllB ...) => BarbieHKD ...
would be the right call? Alternatively if there's another way of doing this then let me know.
I haven't thought about them in the context of higgledy but I want to get the ball rolling. Have you already considered what this would look like?
I'm probably trying to do something that should not be done, but I added recLabels
to a class I have:
class (Eq a, IRes a, Generic a, Ord a, Read a, Show a, Typeable a)
=> IsDerivedRec a where
-- ...
tableName :: TableName
tableName = type2tblName $ typeRep $ Proxy @a
recLabels :: Labels a
recLabels = label
Resulting in the error:
• Could not deduce (Data.Generic.HKD.Labels.GLabels
(GHC.Generics.Rep a))
arising from a use of ‘label’
from the context: IsDerivedRec a
bound by the class declaration for ‘IsDerivedRec’
at src/FDS/Data/IsRecord.hs:36:6-17
• In the expression: label
In an equation for ‘recLabels’: recLabels = label
|
46 | recLabels = label
| ^^^^^
Just curious if it is possible to make this work, or perhaps why not.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.