2

I'm trying to implement the simplest profunctor optic in Idris. Iso is a function that is supposed to be polymorphic in all profunctors. I think that's the correct syntax.

Everything type-checks, except for the final test.

interface Profunctor (p : Type -> Type -> Type) where
  dimap : (a' -> a) -> (b -> b') -> p a b -> p a' b'

Iso : Type -> Type -> Type -> Type -> Type
Iso a b s t = {p : Type -> Type -> Type} -> Profunctor p => p a b -> p s t

-- A pair of functions
data PairFun : Type -> Type -> Type -> Type -> Type where
  MkPair : (s -> a) -> (b -> t) -> PairFun a b s t

-- PairFun a b s t  is a Profunctor in s t
Profunctor (PairFun a b) where
  dimap f g (MkPair get set) = MkPair (get . f) (g . set)

-- Extract a pair of functions from an Iso
fromIso : Iso a b s t -> PairFun a b s t
fromIso iso = iso (MkPair id id)

-- Turn a pair of functions to an Iso
toIso : PairFun a b s t -> Iso a b s t
toIso (MkPair get set) = dimap get set

-- forall p. Profunctor p => p Int Int -> p Char String
myIso : Iso Int Int Char String
myIso = toIso (MkPair ord show)

x : PairFun Int Int Char String
x = fromIso myIso

I'm getting this error. It looks like Idris is complaining about the assumption that p1 is a Profunctor, but that's the constraint in the definition of Iso.

Can't find implementation for Profunctor p1
    Type mismatch between
            p1 Int Int -> p1 Char String (Type of myIso p1
                                                         constraint)
    and
            p Int Int -> p Char String (Expected type)
                
     Specifically:
            Type mismatch between
                    p1 Int Int
            and
                    p Int Int
Bartosz Milewski
  • 11,012
  • 5
  • 36
  • 45
  • It works in Idris 2 if I prefix `{a : _} -> {b : _} -> ` to the Profunctor instance and to fromIso. – Sjoerd Visscher Dec 01 '21 at 12:47
  • 1
    Works in Idris 2 0.3 for me too if I use `0 p` in `Profunctor` and `Iso`, which I believe is the more correct reproduction of the Haskell `Profunctor`. – András Kovács Dec 01 '21 at 12:56
  • BTW the error here is probably lack of generativity of type functions, in GHC `f a ~ g a` implies `f ~ g`, in Idris `f` and `g` can be arbitrary functions (not just constructors), so the same does not follow. Idris 2 is probably just more liberal in guessing generativity in stuck problems. – András Kovács Dec 01 '21 at 12:58
  • It's generally quite important in Idris2 to keep track of what is erasable by adding `0` modalities to arguments. – Alexander Gryzlov Dec 01 '21 at 20:38
  • @AndrásKovács : I was able to use `0 p` in `Iso` (wiht `%language LinearTypes` pragma), but it won't let me do it in `Profunctor`. What's the exact syntax there? So to implement parametric polymorphism I have to erase `p` by translating `forall p` to `0 p`? – Bartosz Milewski Dec 01 '21 at 20:48

1 Answers1

3

The following code worked for me in Idris 2 version 0.3. This is a fairly old version of Idris 2, but it probably works in more recent versions too.

interface Profunctor (0 p : Type -> Type -> Type) where
  dimap : (a' -> a) -> (b -> b') -> p a b -> p a' b'

Iso : Type -> Type -> Type -> Type -> Type
Iso a b s t = {0 p : Type -> Type -> Type} -> Profunctor p => p a b -> p s t

data PairFun : Type -> Type -> Type -> Type -> Type where
  MkPair : (s -> a) -> (b -> t) -> PairFun a b s t

Profunctor (PairFun a b) where
  dimap f g (MkPair get set) = MkPair (get . f) (g . set)

fromIso : Iso a b s t -> PairFun a b s t
fromIso iso = iso (MkPair id id)

toIso : PairFun a b s t -> Iso a b s t
toIso (MkPair get set) = dimap get set

myIso : Iso Int Int Char String
myIso = toIso (MkPair ord show)

x : PairFun Int Int Char String
x = fromIso myIso

Unfortunately I don't know how to make this work in Idris 1. There the issue appears to be generativity of p: the elaborator does not infer p1 = p2 from p1 a b = p2 a b. In any of the Idrises, this does not generally hold, because p1 and p2 can be arbitrary functions. Idris 2 appears to proceed to p1 = p2 anyway at some point; this is a convenience feature at the cost of some robustness of inference.

The irrelevance annotations on p in the above code are not related to the generativity issue that I've just mentioned, they are just required to reproduce the Idris 1 and GHC behavior. In Idris 2, implicitly introduced variables always have 0 multiplicity, so we have to make the p erased as well, to be able to apply it to 0 type parameters. Moreover, the 0 p matches Idris 1 / GHC behavior where types in general are erased. In Idris 2, types are only erased when bound with 0.

András Kovács
  • 29,931
  • 3
  • 53
  • 99
  • The most recent Idris 2 version doesn't like the `0 p` in `Iso`. ("p is not accessible in this context.") – Sjoerd Visscher Dec 02 '21 at 12:41
  • So there are two separate problems: In Idris 2 higher-order interfaces should be annotated with 0. I just checked that this is true for the Functor interface as well. The other problem is more serious: How do you define a type that's parametrically polymorphic: here, forall Profunctor p. Is parametricity even possible in a language with type functions? – Bartosz Milewski Dec 02 '21 at 19:44
  • @BartoszMilewski parametricity is orthogonal to type functions. The usual dependent type theories are parametric and have type functions. – András Kovács Dec 02 '21 at 20:09
  • I get it now. Parametricity in this case means that the mapping p a b-> p s t must use the same formula for all profunctors. The only thing at its disposal is therefore the implementation of `dimap`, which is (implicitly) passed to it. That's why the information about what specific profunctor was passed must be erased. [Too bad different versions of Idris still haven't converged on the correct behavior.] – Bartosz Milewski Dec 02 '21 at 20:24