Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generalised newtype deriving on class functions with Functors

Tags:

haskell

I'm developing a class representing key/value mappings, and I've got a function which is basically like alterF:

class C t where
  ...
  alterF :: Functor f => 
    (Maybe (Value t) -> f (Maybe (Value t))) -> Key t -> t -> f t

Unfortunately, this breaks GeneralisedNewtypeDeriving. In some cases, this is reasonable, as GeneralisedNewtypeDeriving from what I understand essentially uses Coercible and the function coerce. Coercible represents types which are representationally equal, i.e. they have the same representation at run time, so we can convert between them for free. For example, given:

newtype T a = T a

we have:

Coercible a (T a)
Coercible (T a) a

but we don't have (in general):

Coercible (f a) (f (T a))
Coercible (f (T a)) (f a)

For example, GADTs violate this representational equality. But there are lots of values of f that do work. For example:

Coercible (Maybe a) (Maybe (T a))
Coercible (Maybe (T a)) (Maybe a)
Coercible [a] [T a]
Coercible [T a] [a]
Coercible (Identity a) (Identity (T a))
Coercible (Identity (T a)) (Identity a)

It also occurs to me that this instance could be written:

Functor f => Coercible (f a) (f (T a))
Functor f => Coercible (f (T a)) (f a)

Just using fmap. Unlike the usual coerce, this wouldn't be free at runtime, but it will work.

So I've got a class with 10 functions, 9 of which work fine with GeneralisedNewtypeDeriving. There's just this final one which doesn't, which could be resolved mechanically using fmap. Do I have to write custom wrapping/unwrapping implementations for all my class functions, or is there a way to either require me to write the implementation for just the problem function or alternatively coax GHC into using fmap as part of it's GeneralisedNewtypeDeriving?

like image 572
Clinton Avatar asked Feb 18 '18 04:02

Clinton


People also ask

How to add new functionality to a derived class in Java?

To add new functionality to a derived class, simply declare that functionality in the derived class like normal: Now the public will be able to call getValue () on an object of type Derived to access the value of m_value. This produces the result:

What is a functor?

What a mess. This is where functors come into use. A functor (or function object) is a C++ class that acts like a function. Functors are called using the same old function call syntax.

When should you use functors instead of global variables?

One obvious answer might be global variables. However, good coding practices do not advocate the use of global variables and say they must be used only when there is no other alternative. Functors are objects that can be treated as though they are a function or function pointer.

Why can't I get the getValue () function in derived?

Although it may be obvious, objects of type Base have no access to the getValue () function in Derived. The following does not work: This is because there is no getValue () function in Base. Function getValue () belongs to Derived. Because Derived is a Base, Derived has access to stuff in Base.


1 Answers

If f is a Functor, you can make a "representational wrapper" for it

data Rep f a where
    Rep :: (b -> a) -> f b -> Rep f a

which is isomorphic to f except that it is representational in a, by what is essentially existential quantification over any nominal variance f might have. I think this construction happens to have some fancy category theory name but I don't remember what it is. To get the f a back out of a Rep f a, you need to use f's Functorhood.

You can use this wrapper in your method, ensuring that your class varies representationally.

alterFRep :: (Functor f) 
          => (Maybe (Value t) -> Rep f (Maybe (Value t))) -> Key t -> t -> Rep f t

And then make the real "method" just a regular function in terms of it by using the isomorphism with Rep f. You can also make a convenience method for instance authors:

toAlterFRep :: 
    (forall f t. (Functor f) => (Maybe (Value t) -> f (Maybe (Value t))) -> Key t -> t -> f t)
 -> (forall f t. (Functor f) => (Maybe (Value t) -> Rep f (Maybe (Value t))) -> Key t -> t -> Rep f t)

so they don't have to worry about what the heck Rep is, they just implement alterF normally and use toAlterFRep on it.

like image 126
luqui Avatar answered Oct 03 '22 05:10

luqui