Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Couple things that might be useful to have #39

Open
masaeedu opened this issue Oct 1, 2021 · 9 comments
Open

Couple things that might be useful to have #39

masaeedu opened this issue Oct 1, 2021 · 9 comments

Comments

@masaeedu
Copy link

masaeedu commented Oct 1, 2021

Hello there. I was wondering if you'd be amenable to adding the following classes (and ways of deriving them):

type Lens' s a = Lens' { project :: forall f. Functor f => (a -> f a) -> s -> f s }

type ProductLike :: (k -> Type) -> Type
class ProductLike t
  where
  lenses :: t (Lens' (t Identity))

and:

type BTraversable1 :: (k -> Type) -> Type
class BTraversable t => BTraversable1 t
  where
  btraverse1 :: Apply e => (forall a. f a -> e (g a)) -> b f -> e (b g) 

There might be some way to turn ProductLike in a sensible way (perhaps involving Co) but based on my rushed thinking so far I haven't been able to think of anything.

@jcpetruzza
Copy link
Owner

I considered something like lenses in the past (maybe for any functor, not just Identity?) but I'm not sure I see usages that are not covered well by generic-lens already. Do you have scenarios where you'd like to operate on all lenses at once?

For btraverse1, would one need a new class? E.g., can't one derive it from btraverse and an auxiliary newtype wrapper that pretends an Apply is actually an Applicative, or something like that?

@masaeedu
Copy link
Author

masaeedu commented Oct 10, 2021

For btraverse1, would one need a new class? E.g., can't one derive it from btraverse and an auxiliary newtype wrapper that pretends an Apply is actually an Applicative, or something like that?

@jcpetruzza That makes sense at a high level, but I haven't been able to make it work. I think the relevant newtype is MaybeApply f a = Either (f a) a (where Apply f => Applicative (MaybeApply f)). But in order to convert a traversal over MaybeApply f into a traversal over f itself, you end up needing an operation Apply f => Either (f (b g)) (b g) -> f (b g), and I'm honestly not sure how to supply that in general for a "TraversableB HKD with at least one position".

You can instead make it work with something like this as the primitive operation:

bhead :: b f -> Some b

or even:

brefute :: b VoidF -> Void

But this is a bit more mysterious for an implementor.

@jcpetruzza
Copy link
Owner

jcpetruzza commented Oct 10, 2021

I think I wasn't really very clear, sorry! What I meant is that since "Apply is Applicative without pure", and btraverse requires an Applicative but doesn't use pure, we can use a newtype wrapper to pretend an Apply is an Applicative whose pure will never be called. Something like this:

newtype CantUsePure f a = CantUsePure (f a)
  deriving newtype Functor

instance Apply f => Applicative (CantUsePure f) where
  pure = error "Can't use pure!"
  CantUsePure f <*> CantUsePure x = CantUsePure (f <.> x)

btraverse1
  :: (TraversableB b, Apply e)
  => (forall a . f a -> e (g a))
  -> b f
  -> e (b g)
btraverse1 h
  = coerce . btraverse (CantUsePure . h)

Of course, one shouldn't export CantUsePure.

I think I rather not introduce a dependency just for this type of functions; and one could put them in a separate package, anyway

@masaeedu
Copy link
Author

@jcpetruzza The main issue as I understand it is that the quality of "not using pure" isn't attached to f, but to b. In a context where you're abstract in b, and want to demand that b has at least one "position" where f occurs, there doesn't seem to be any safe way to get around requiring a subclass of TraversableB (although the best way to encode its operations is debatable).

Of course we can just use the error technique to bypass the type system and pretend that any TraversableB is Traversable1B, and then blow up at runtime if we've made a mistake. This doesn't seem ideal though, and I'd rather not lie to the consumer of my API in this way.

@jcpetruzza
Copy link
Owner

jcpetruzza commented Oct 10, 2021

You are correct, if you have something that is an Apply and not an Applicative, this method would work as a backdoor only if you control the instances on which you use it. I thought it would always work with the generic instances we provide, but this is not true, since it will currently blow on types that have values not under f.

I still think that there is no difference between TraversableB and Traversable1B, in that, afaics, both have exactly the same instances. If Apply were a super-class of Applicative, we'd just require Apply for the type of the effect instead (the generic instances would just be a bit more complex to write, I think)

@jcpetruzza
Copy link
Owner

On further thought, Traversable1B has fewer instances than TraversableB, I think. One can't get an instance for:

data Unit (f :: k -> Type) = Unit

or, more in general, for any type with constructors where f does not occur.

@masaeedu
Copy link
Author

masaeedu commented Oct 10, 2021

@jcpetruzza Yes, exactly. This is why you would want Traversable1B to be a subclass of TraversableB.

If Apply were a super-class of Applicative, we'd just require Apply for the type of the effect instead (the generic instances would just be a bit more complex to write, I think)

Things are a bit confusing here because the relationship between Traversable/Traversable1B and Applicative/Apply is contravariant, so the variance is flipped around. Traversable is a superclass of Traversable1 as a consequence of Applicative being a subclass of Apply.

@fumieval
Copy link
Contributor

FYI barbies-th provides an equivalent AccessorsB class: https://hackage.haskell.org/package/barbies-th-0.1.9/docs/Barbies-TH.html#t:AccessorsB

It would be great to have this in barbies so that barbies-th focus on the Template Haskell part

@masaeedu
Copy link
Author

masaeedu commented Jan 8, 2022

@fumieval FWIW i've revised my position on the "bag of lenses" class. I now believe that what we want is the class of HKDs that are products. A naive encoding of this is pretty straightforward:

type HKD k = (k -> Type) -> Type

type HList :: [k] -> HKD k
data HList xs f
  where
  HNil :: HList '[] f
  HCons :: f x -> HList xs f -> HList (x ': xs) f

type ProductB :: HKD k -> Constraint
class ProductB (b :: HKD k)
  where
  type Components b :: [k]
  to :: b f -> HList (Components b) f
  from :: HList (Components b) f

-- Laws:
-- to . from = id
-- from . to = id

I believe this would subsume ConstraintsB, and would also supply the bag of lenses we're looking for in certain circumstances. Additionally, it provides a good basis for discussing the duality of products and coproducts and their interactions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants