Reputation: 3074
My knowledge of category theory isn’t very good. So please bear with me.
I’ve been reading Monads Made Difficult
and saw the following definition.
class (Category c, Category d) => Functor c d t where
fmap :: c a b -> d (t a) (t b)
from that site I read that the Functor type in Haskell’s prelude is really an Endofunctor. (where both category c and d in the above class are Hask)
After reading through the page, I was wondering. Would Haskell be better suited for meta-programming if it used real Functors and not just Endofunctors?
say we have the following (the Js stands for Javascript)
data Js a where
litInt :: Integer -> Js Integer
litString :: String -> Js String
var :: String -> Js a
lam :: String -> Js b -> Js (a -> b)
let :: String -> Js a -> Js b -> Js b
ap :: JsFunc a b -> Js a -> Js b
type JsFunc a b = Js (a -> b)
now back to the definition of Functor above
class (Category c, Category d) => Functor c d t where
fmap :: c a b -> d (t a) (t b)
we can have c be JsFunc, d be Hask and have t be Js
this will give us a Functor for Js. That I could not do using Haskell’s Endofunctor.
I could not find anything for the Apply or Applicative type-classes defined in the same way as this Functor. Could the same be done with those too?
And correct me if I’m wrong. But the same can not be done for Monad, because it really an instance of an Endofunctor?
Upvotes: 8
Views: 303
Reputation: 15029
we can have c be JsFunc, d be Hask and have t be Js
Well, no we can't exactly since JsFunc
is an unsaturated type synonym, so we can't instantiate a variable (c
) to it.
You could create a newtype
newtype JsCat a b = JsCat (Js (a -> b))
and then create a Functor JsCat (->) Js
like you say.
However, this doesn't give you anything new since if we expand what you wanted the type of fmap
to be, it becomes
fmap :: JsFunc a b -> (->) (Js a) (Js b) -- or
fmap :: Js (a -> b) -> Js a -> Js b
which is none other than an Apply
instance for Js
. So, I'm not sure why you want to make this into a functor when it already fits into an existing abstraction.
Upvotes: 2
Reputation: 120751
Well, first –
... if it used real Functors and not just Endofunctors ...
Haskell functors are real functors. Only, the Functor
class doesn't allow any kind of general functors, but only the specific ones that are endofunctors on Hask.
Indeed, non-endo–functors are interesting; mathematicians use them all the time. And while as you say, there can't be non-endofunctor monads, an analogue to Applicative
is possible: that class is really just a nice Haskell interface for monoidal functors, which can be defined between different categories. Looks about like this:
class (Functor r t f, Category r, Category t) => Monoidal r t f where
pureUnit :: () `t` f ()
fzipWith :: (a, b) `r` c -> (f a, f b) `t` f c
To actually use this anywhere as conveniently as the standard Applicative
class though, you'll need a bit more than just the Monoidal
class. This has been tryied in a couple of libraries. I also wrote one. But, well... it doesn't exactly look beautiful...
class (Functor f r t, Cartesian r, Cartesian t) => Monoidal f r t where
pureUnit :: UnitObject t `t` f (UnitObject r)
fzipWith :: (ObjectPair r a b, Object r c, ObjectPair t (f a) (f b), Object t (f c))
=> r (a, b) c -> t (f a, f b) (f c)
class (Monoidal f r t, Curry r, Curry t) => Applicative f r t where
-- ^ Note that this tends to make little sense for non-endofunctors.
-- Consider using 'constPure' instead.
pure :: (Object r a, Object t (f a)) => a `t` f a
(<*>) :: ( ObjectMorphism r a b
, ObjectMorphism t (f a) (f b), Object t (t (f a) (f b))
, ObjectPair r (r a b) a, ObjectPair t (f (r a b)) (f a)
, Object r a, Object r b )
=> f (r a b) `t` t (f a) (f b)
(<*>) = curry (fzipWith $ uncurry id)
I don't know if any of these frameworks would allow you to write the applicative JS code you want in a practical way. Possibly, but actually I rather suspect it would get very messy. Also note that, while you now have applicatives floating around, this doesn't mean you can actually do what's normally done with Hask applicatives in Js code – on the contrary, you'd actually need to wrap those applicatives as transformers around the Js
type.
You might want to consider abandoning “JS values” as such completely, and only write in a category/arrow paradigm instead. I.e., turn the definition around:
data JsFunc a b where
litInt :: Integer -> JsFunc () Integer
litString :: String -> JsFunc () String
var :: String -> JsFunc () a
lam :: String -> JsFunc () b -> JsFunc a b
let :: String -> JsFunc () a -> JsFunc () b -> JsFunc () b
ap :: JsFunc a b -> JsFunc () a -> JsFunc () b
type Js = JsFunc ()
here, we use ()
as the terminal object of the JsFunc
category – which makes the Js a ≡ JsFunc () a
arrows equivalent to what we normally call values (at least if we don't consider strictness semantics). If you go that route, pretty much everything needs to be written point-free, but there's syntax to pretend otherwise, and you can nicely incorporate functors and even monads (as Kleisli arrows).
Upvotes: 3
Reputation: 153172
According to this mailing list post, applicative functors are known in the math literature as "weakly symmetric lax monoidal functors". A (brief, not careful) look at the nlab page for lax monoidal suggests that this concept is also parameterized by two categories, so you can probably extend this parametric functor class to a parametric applicative class with some work.
As you say, monads are endofunctors, so they can't be parameterized by two categories. But the one category they have as a parameter need not be Hask; so one could give a parameterized monad as well, like
class Functor c c f => Monad c f where
return :: c a (f a)
join :: c (f (f a)) (f a)
Then we can use a sort of standard trick to define bind:
(=<<) :: Monad c m => c a (m b) -> c (m a) (m b)
(=<<) f = join . fmap f
The (.)
operation here is the composition from the Category
class, not function composition from Prelude
.
Upvotes: 3