reifying based-on type of a newtype or data

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

reifying based-on type of a newtype or data

AntC
[reposted from Beginners, where it met stoney silence.]

So I have (or rather the user of my package has):

> {-# LANGUAGE  DeriveDataTypeable    #-}
>
>    newtype Foo = Foo Int    deriving (Read, Show, Typeable, Data, ...)
>    someFoo     = Foo 7
>

Note:
* the `newtype` could be `data` -- if that would help.
* this is _not_ a parameterised type, but a 'baked in' `Int`.
* the data constr is named same as the type -- if that would help.

I can ask for `typeOf someFoo` and get `Foo` OK.
I can ask for `typeOf Foo`  and get `Int -> Foo` OK.

If I ask for `typeOf (typeOf someFoo)` I get `TypeRep`.
`typeOf (show $ typeOf someFoo`) gets me `[Char]` (aka `String`)

So far very logical, but not very helpful.

What I want is to get the based-on type baked inside `someFoo`
-- that is: `Int`
(It would also be handy to get the name of the data constr, just in case
it's different to the type.)

Do I need to get into `deriving (..., Generic)` ?

That looks like serious machinery!

Thanks
AntC




_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: reifying based-on type of a newtype or data

John Lato-2
What about Data.Typeable.typeRepArgs ?

typeRepArgs :: TypeRep -> [TypeRep]

Prelude Data.Data> typeRepArgs (typeOf Foo)
[Int,Foo]

For any function type, the head of typeRepArgs should be the type of the first parameter.  For non-function types, it looks like typeRepArgs returns an empty list.

For anything more complicated, I suspect you'll need Data/Generic/Template Haskell.

John L.


On Mon, Oct 28, 2013 at 7:15 PM, AntC <[hidden email]> wrote:
[reposted from Beginners, where it met stoney silence.]

So I have (or rather the user of my package has):

> {-# LANGUAGE  DeriveDataTypeable    #-}
>
>    newtype Foo = Foo Int    deriving (Read, Show, Typeable, Data, ...)
>    someFoo     = Foo 7
>

Note:
* the `newtype` could be `data` -- if that would help.
* this is _not_ a parameterised type, but a 'baked in' `Int`.
* the data constr is named same as the type -- if that would help.

I can ask for `typeOf someFoo` and get `Foo` OK.
I can ask for `typeOf Foo`  and get `Int -> Foo` OK.

If I ask for `typeOf (typeOf someFoo)` I get `TypeRep`.
`typeOf (show $ typeOf someFoo`) gets me `[Char]` (aka `String`)

So far very logical, but not very helpful.

What I want is to get the based-on type baked inside `someFoo`
-- that is: `Int`
(It would also be handy to get the name of the data constr, just in case
it's different to the type.)

Do I need to get into `deriving (..., Generic)` ?

That looks like serious machinery!

Thanks
AntC




_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: reifying based-on type of a newtype or data

AntC
In reply to this post by AntC
> John Lato <jwlato <at> gmail.com> writes:
>
> What about Data.Typeable.typeRepArgs ?
> typeRepArgs :: TypeRep -> [TypeRep]
> Prelude Data.Data> typeRepArgs (typeOf Foo)[Int,Foo]

Thanks John, I did look at that,
and thought it returned only type args,
not the 'baked in' type.

Your example applies to `(typeOf Foo)` -- that is, the data constr,
which is indeed a function.
And `typeOf Foo` is `Int -> Foo`, as I noted in the OP.

I want something that can apply to a value of type Foo.
(`someFoo`, in my example.)

> ...

> For anything more complicated, I suspect you'll need
> Data/Generic/Template Haskell.

That is rather what I feared.
I want to avoid TH, because that imposes on my user.

Could you point to where/what in Data/Generics?

> >
> >    newtype Foo = Foo Int    deriving (Read, Show, Typeable, Data, ...)
> >    someFoo     = Foo 7
> >
> > I can ask for `typeOf someFoo` and get `Foo` OK.
> > I can ask for `typeOf Foo`  and get `Int -> Foo` OK.
> > If I ask for `typeOf (typeOf someFoo)` I get `TypeRep`.
> > `typeOf (show $ typeOf someFoo`) gets me `[Char]` (aka `String`)
> >
> > What I want is to get the based-on type baked inside `someFoo`




_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: reifying based-on type of a newtype or data

John Lato-2
On Mon, Oct 28, 2013 at 11:07 PM, AntC <[hidden email]> wrote:
> John Lato <jwlato <at> gmail.com> writes:
>
> What about Data.Typeable.typeRepArgs ?
> typeRepArgs :: TypeRep -> [TypeRep]
> Prelude Data.Data> typeRepArgs (typeOf Foo)[Int,Foo]

Thanks John, I did look at that,
and thought it returned only type args,
not the 'baked in' type.

Your example applies to `(typeOf Foo)` -- that is, the data constr,
which is indeed a function.
And `typeOf Foo` is `Int -> Foo`, as I noted in the OP.

I want something that can apply to a value of type Foo.
(`someFoo`, in my example.)

Ah, sorry.  I understand now.  First, let's define:

newtype FooT = FooD Int deriving (Data, Typeable, Generic)

someFoo :: FooT
someFoo = FooD 7

so we can keep all the Foo's straight.

Personally I think it's a little more straightforward to do this with Data, but Generics have some benefits as well, and are probably simpler to pick up if you don't have much experience with either.

With Data.Data, we can get the name of the data constructor used to create a 'FooT' by using 'toConstr'

*Foo> toConstr someFoo
FooD

There's not a whole lot we can do with a Constr.  But we can find out the types of the inner values by mapping over the data structure with gmapM.  gmapM has a hairy type,

gmapM :: (Monad m, Data a) => (forall d. Data d => d -> m d) -> a -> m a

but it's sufficient for this purpose.  Since Data implies Typeable, all you need to do is call typeOf on the internal values and write them to the monad!

*Foo >let (_,types) = runWriter $ gmapM (\d -> tell [typeOf d] >> return d) someFoo
*Foo>> :t types
types :: [TypeRep]
*Foo> types
[Int]

You can do something similar with Generics.  The types look bigger, but the functions are probably simpler to grok.  And once you start using generics the types get much easier to read.

*Foo GHC.Generics> let x = from someFoo
*Foo GHC.Generics> :t x
x :: D1 Foo.D1FooT (C1 Foo.C1_0FooT (S1 NoSelector (Rec0 Int))) x

Deriving a Generic instance generates a bunch of auxiliary types, which contain the same information you could get out of DataRep/Constr.  The generated structure is fairly uniform: D1 is for datatype information, C1 is constructor information, and S1 is selector information (for record syntax).  These are all type synonyms around the M1 constructor, used for meta-information.  So you can just peel of the M1s to get to the information you want.

*Foo GHC.Generics> datatypeName x
"FooT"
*Foo GHC.Generics> let (M1 xc) = x
*Foo GHC.Generics> :t xc
xc :: C1 Foo.C1_0FooT (S1 NoSelector (Rec0 Int)) t
*Foo GHC.Generics> conName xc
"FooD"
*Foo GHC.Generics> let (M1 xs) = xc
*Foo GHC.Generics> :t xs
xs :: S1 NoSelector (Rec0 Int) t
*Foo GHC.Generics> let (M1 xr) = xs
*Foo GHC.Generics> :t xr
xr :: Rec0 Int t

Rec0 is not meta-information, rather it encodes recursion, which means it has the actual value we're interested in.  The constructor is K1.

*Foo GHC.Generics> let (K1 xi) = xr
*Foo GHC.Generics> :t xi
xi :: Int
*Foo GHC.Generics> xi
7

At this point, you can get 'typeOf xi', or you could even use `asTypeOf` (or various other methods) to expose the type directly.

The usual way to use Generic is to make a class that performs the operations you want, then make instances of that class for (M1 C a b), (M1 D a b), etc.  Although if you control the type, you could hard-wire unwrapping M1/K1 as I did above.  Doing so is type-safe, since if either Foo or the internal deriving structure changes, the generated representation won't match the unwrapping.  Plus this is the easiest way to expose the 'Int' type to the type system.

I've created a gist that should get you started with the generic class creation if you decide to go that route, https://gist.github.com/JohnLato/7209766.

John

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: reifying based-on type of a newtype or data

oleg-30
In reply to this post by AntC

Anthony Clayden wrote:
>    newtype Foo = Foo Int    deriving (Read, Show, Typeable, Data, ...)
>    someFoo     = Foo 7
> ...
> What I want is to get the based-on type baked inside `someFoo`
> -- that is: `Int`
> (It would also be handy to get the name of the data constr, just in case
> it's different to the type.)

If we have a data type declaration
        data C a b = D1 a Int b | D2 a | D3 [a]

we use methods of module Typeable to get information about the
left-hand-side of the equation (about the type expression C a b
and its structure, an application of a type constructor C to two
arguments). We use methods of module Data.Data to get information
about the right-hand-side of the equation (about _data_ constructors
D1, D2 and D3 and their arguments).

Here is an example:

> {-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables #-}
>
> import Data.Data
>
> data C a b = D1 a Int b | D2 a | D3 [a] deriving (Data, Typeable)

A sample datum of type C

> someC = undefined :: C Int Char

*Main> typeOf someC
C Int Char

meaning someC has the type C Int Char.

*Main> dataTypeOf someC
DataType {tycon = "Main.C", datarep = AlgRep [D1,D2,D3]}

Now we see something about the right-hand-side  of the equation
defining C: C a b is a data type and it has three data constructors, D1, D2
and D3.

How to get information about their arguments? First, we extract
constructors

> getCtor :: Data a => Int -> a -> Constr
> getCtor i x = case dataTypeRep $ dataTypeOf x of AlgRep cs -> cs !! i

*Main> getCtor 0 someC
D1

Now, we reify a data constructor by unfolding it in a particular way

> newtype DTInfo a = DTInfo [TypeRep]

> -- Get information about the given ctor of a given algebraic data type
> -- (the data type value may be undefined -- we only need its type)
> ctorInfo :: forall a. Data a => a -> Constr -> [TypeRep]
> ctorInfo _ ctor = case go ctor of DTInfo reps -> reverse reps
>   where
>   go :: Constr -> DTInfo a
>   go = gunfold (\ (DTInfo infos :: DTInfo (b->r)) ->
>                   DTInfo (typeOf (undefined:: b) : infos))
>                (\r -> DTInfo [])

*Main> ctorInfo someC $ getCtor 0 someC
[Int,Int,Char]

meaning that D1 of the type C Int Char has three arguments,
Int, Int, Char -- in that order.

*Main> ctorInfo someC $ getCtor 1 someC
[Int]

*Main> ctorInfo someC $ getCtor 2 someC
[[Int]]

It is easy to answer the original question about someFoo

*Main> getCtor 0 someFoo
Foo

*Main> ctorInfo someFoo $ getCtor 0 someFoo
[Int]

-- A faster way for a defined datum
*Main> ctorInfo someFoo $ toConstr someFoo
[Int]

So, someFoo is constructed by data constructor Foo and that data
constructor has one Int argument.

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: reifying based-on type of a newtype or data

AntC
> <oleg <at> okmij.org> writes:

> ...
>
> It is easy to answer the original question about someFoo
>
> ...

So it is! (For some value of `easy` ;-)

Thank you both Oleg and John.

Is all that documented somewhere 'official'?
(I did try various searches and wikis, and look at Data.Data on Hackage.
 But nothing seemed to hang together enough.
 Perhaps my question is an unusual use case?)

AntC



_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: reifying based-on type of a newtype or data

John Lato-2
The Scrap Your Boilerplate (SYB) papers are probably the best resource for Data.Data.  I'm not aware of a similar resource on GHC Generics.

I think the problem you had is that your original question is a very small fragment of the problems generics are meant to solve.  For people used to generics systems (Oleg, not really me!), your question is near-trivial.  But you need a decent familiarity with the whole apparatus to see how to use any of it.

(I still think 'mapQ typeOf' is likely to be the most concise solution).



On Tue, Oct 29, 2013 at 5:19 AM, AntC <[hidden email]> wrote:
> <oleg <at> okmij.org> writes:

> ...
>
> It is easy to answer the original question about someFoo
>
> ...

So it is! (For some value of `easy` ;-)

Thank you both Oleg and John.

Is all that documented somewhere 'official'?
(I did try various searches and wikis, and look at Data.Data on Hackage.
 But nothing seemed to hang together enough.
 Perhaps my question is an unusual use case?)

AntC



_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe