1

I want to make a pair type to represent modular arithmetic. I made its constructor

{- LANGUAGE GADTs -}
data Zn e where
    Zn :: Integer -> Integer -> Zn Integer

because I would like to be able to fmap over it and all the things. So if I try to make it a Functor instance

instance Functor Zn where
    fmap f (Zn s n) = Zn s $ mod (f n) s

I get Could not deduce: b ~ Integer from the context: a ~ Integer. Of course it cannot deduce this because this data type does not have a meaningful notion of fmap :: (a -> b) -> Zn a -> Zn b for all a and b, but it does whenever a and b are such that one could actually construct Zn instances from them (ie Integer). I also tried having the Zn constructor and all methods require Integral e but I get a similar problem. In that case, s has type a so constructing a Zn with it and f n :: b fails. In the second case I could convert s from a to Integer and then to b but this is clunky. The whole thing is a bit clunky. I'm just doing it because I want to be able to implement Functor and Applicative to perform mod after mapping a function so I can just implement + as liftA2 (+) and so on. The modulus s should be part of the type but dependent types are not practical in Haskell as far as I know.

Is it possible to have a type with kind * -> * implement Functor only for some arguments?

hacatu
  • 638
  • 6
  • 15
  • If you're trying to make that type into a number, I think your `s` needs to be at type level. Otherwise, someone could give you `Zn 3 2 + Zn 8 7`, and what would you return then? – Joseph Sible-Reinstate Monica Apr 25 '20 at 14:09
  • It would be ideal to have `s` in the type, but I think having it be a runtime error is better than using the `Nat` trick to encode numbers in types. I would put the modulus in the type if Haskell had more direct support for dependent types. – hacatu Apr 25 '20 at 15:05

1 Answers1

3

You're looking for MonoFunctor. It's just like Functor, but works with a fixed inner type instead of being parametrically polymorphic. Here's how you'd implement it for your type:

{-# LANGUAGE TypeFamilies #-}

import Data.MonoTraversable

data Zn = Zn Integer Integer

type instance Element Zn = Integer

instance MonoFunctor Zn where
    omap f (Zn s n) = Zn s $ mod (f n) s

However, be warned that such an instance isn't exactly lawful. You may be better off just making a modMap function or something, to avoid people making bad assumptions about how it will work.

  • 1
    I think this is at least morally lawful. As far as I can see, the only law which is broken is `omap id = id` which fails e.g. for `Zn 2 10` because `10` should have been `0`. If we consider the values up to equivalence, I think it should be lawful. – chi Apr 25 '20 at 08:58
  • @chi It breaks the other law too. ``let f x = x `mod` 8; g x = x * 6; m = Zn 9 4``. Now `omap f (omap g m)` is 6, but `omap (f . g) m` is 0. – Joseph Sible-Reinstate Monica Apr 25 '20 at 13:56
  • True. The problem if that your `f` does not respect the equivalence. One needs to consider as morphisms only those `f`s such that `mod s x = mod s y ==> mod s (f x) = mod s (f y)`. So it is a mono-functor, I guess, but only on a suitable subcategory (which can not be enforced using types.) – chi Apr 25 '20 at 14:02
  • 1
    @chi I feel like it's REALLY stretching "morally lawful" to make that argument though. – Joseph Sible-Reinstate Monica Apr 25 '20 at 14:10
  • 1
    I agree. That's too much. – chi Apr 25 '20 at 14:29
  • 1
    Interestingly the MonoTraversable library does not include `MonoApplicative` so one could add that class, but for now I'm just using `modMap` and `modLift2`. Applicative is problematic anyway because `pure` doesn't know the modulus. – hacatu Apr 25 '20 at 15:13
  • I'm accepting this answer for now because it solves the problem in this case and I don't think Haskell allows "partial" implementation of typeclasses. – hacatu Apr 25 '20 at 15:16