Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functor/Applicative-like typeclass for composing (a->a) functions instead of (a->b)?

Tags:

haskell

I have a type that looks like this:

newtype Canonical Int = Canonical Int

and a function

canonicalize :: Int  -> Canonical  Int
canonicalize =  Canonical . (`mod` 10)  -- or whatever

(The Canonical type may not be important, it just serves to distinguish "raw" values from "canonicalized" values.)

I'd like to create some machinery so that I can canonicalize the results of function applications.

For example: (Edit: fixed bogus definitions)

cmap :: (b->Int) -> (Canonical b) -> (Canonical Int)
cmap f (Canonical x) = canonicalize $ f x

cmap2 :: (b->c->Int) -> (Canonical b) -> (Canonical c) -> (Canonical Int)
cmap2 f (Canonical x) (Canonical y) = canonicalize $ f x y

That's superficially similar to Functor and Applicative, but it isn't quite, because it's too specialized: I can't actually compose functions (as required by the homomorphism laws for Functor/Applicative) unless 'b' is Int.

My goal is to use existing library functions/combinators, instead of writing my own variants like cmap, cmap2. Is that possible? Is there a different typeclass, or a different way to structure Canonical type, to enable my goal?

I've tried other structures, like

newtype Canonical a = Canonical { value :: a, canonicalizer :: a -> a }

but that hits the same non-composability problem, because I can't translate one canonicalizer to another (I just want to use the canonicalizer of the result type, which is always Int (or Integral a)

And I can't force "specialization-only" like so, this isn't valid Haskell:

instance (Functor Int) (Canonical Int) 

(and similar variations)

I also tried

newtype (Integral a) => Canonical a = Canonical a -- -XDatatypeContexts
instance (Integral a) => Functor Canonical where
  fmap f (Canonical x) = canonicalize  $ f x

but GHC says that DatatypeContexts is deprecated, and a bad idea, and more severely, I get:

 `Could not deduce (Integral a1) arising from a use of 'C'   
 from the context (Integral a)
 bound by the instance declaration
 [...] fmap :: (a1 -> b) -> (C a1 -> C b)

which I think is saying that the constraint Integral a can't actually be used to constrain fmap to (Integral -> Integral) the way I wish, which is sort of obvious (since fmap has two type variables) :-(

And of course this isn't valid Haskell either

instance (Integer a) => Functor Canonical where

Is there a similar typeclass I could use, or am I wrong to try to use a typeclass at all for this functionality of "implicitly canonicalize the results of function calls"?

like image 728
misterbee Avatar asked Feb 15 '23 07:02

misterbee


1 Answers

I think what you're trying to achieve is available in the mono-traversable package, and in this case the MonoFunctor typeclass.

like image 106
Michael Snoyman Avatar answered May 23 '23 07:05

Michael Snoyman