Skip to content

Verifiy the application of instance chain skolem type variables in a second step #4028

@mikesol

Description

@mikesol

Summary

In an instance chain

data Peano
foreign import data Succ :: Peano -> Peano
foreign import data Z :: Peano

instance fooZ :: Foo Z
instance foo2 :: Foo (Succ (Succ Z))
instance fooN :: Foo (Succ n)

Prohibit foo2 and fooN to be chosen for the same input.

Motivation

In the following snippet, two different branches of an instance chain are chosen for the same input:

module Test.Main where

import Prelude

import Control.Monad.Indexed (class IxApplicative, class IxApply, class IxBind, class IxFunctor, class IxMonad)
import Control.Monad.Indexed.Qualified as Ix
import Data.List (List(..), fromFoldable)
import Data.Newtype (class Newtype, unwrap)
import Effect (Effect)
import Effect.Class (class MonadEffect)
import Effect.Class.Console (log)
import Type.Proxy (Proxy(..))

data Unk

foreign import data Unk0 :: Unk

foreign import data UnkX :: Unk -> Unk
newtype MUnk (i :: Unk) (o :: Unk) a
  = MUnk (Effect a)

derive instance newtypeUnk :: Newtype (MUnk i o a) _

derive newtype instance functorMUnk :: Functor (MUnk i o)

derive newtype instance applicativeMUnk :: Applicative (MUnk i o)

derive newtype instance applyMUnk :: Apply (MUnk i o)
derive newtype instance bindMUnk :: Bind (MUnk i o)
derive newtype instance monadMUnk :: Monad (MUnk i o)
derive newtype instance monadEffectMUnk :: MonadEffect (MUnk i o)

instance munkIxFunctor :: IxFunctor MUnk where
  imap f (MUnk a) = MUnk (f <$> a)

instance munkIxApplicative :: IxApply MUnk where
  iapply (MUnk f) (MUnk a) = MUnk (f <*> a)

instance munkIxApply :: IxApplicative MUnk where
  ipure a = MUnk $ pure a

instance munkIxBind :: IxBind MUnk where
  ibind (MUnk monad) function = MUnk (monad >>= (unwrap <<< function))

instance munkIxMonad :: IxMonad MUnk

class TLShow ::  (kType). k  Constraint
class TLShow i where
  tlShow :: Proxy i -> String

instance tlShowUnk2 :: TLShow (UnkX (UnkX Unk0)) where
  tlShow _ = "I'm unk 2"
else instance tlShowUnk0 :: TLShow Unk0 where
  tlShow _ = "Unk0"
else instance tlShowUnkX :: TLShow x => TLShow (UnkX x) where
  tlShow _ = "UnkX (" <> tlShow (Proxy :: Proxy x) <> ")"

class IxRec m where
  next :: forall (i :: Unk) a. Show a => TLShow i => a -> m i (UnkX i) a
  done :: forall (i :: Unk). m i Unk0 Unit

iFor_ :: forall (i :: Unk) m a. Show a => TLShow i => IxRec m => IxMonad m => List a -> m i Unk0 Unit
iFor_ Nil = done
iFor_ (Cons a b) = Ix.do
  _ <- next a
  iFor_ b

printMUnk :: forall (i :: Unk). TLShow i => MUnk i i Unit
printMUnk = Ix.do
  log $ tlShow (Proxy :: Proxy i)

instance ixRec :: IxRec MUnk where
  next a = Ix.do
    printMUnk
    log $ show a
    pure a
  done = pure unit

foreign import arr0 :: Effect (Array Int)

main :: Effect Unit
main = do
  list0 <- fromFoldable <$> arr0
  let (MUnk o) = (iFor_ :: List Int -> MUnk Unk0 Unk0 Unit) list0
  o
  log $ (tlShow (Proxy :: Proxy (UnkX (UnkX (UnkX Unk0)))))

produces

Unk0
1
UnkX (Unk0)
2
UnkX (UnkX (Unk0))
3
UnkX (UnkX (UnkX (Unk0)))
4
UnkX (UnkX (UnkX (UnkX (Unk0))))
5
UnkX (I'm unk 2)

Meaning that "UnkX (UnkX Unk0)" and "I'm unk 2" are both chosen as outputs for the function tlShow :: Proxy i -> String with input UnkX (UnkX Unk0) depending on the control flow of the program. It seems like that should not be allowed.

Proposal

I have no idea how the compiler works with instance chains, but I would assume that it does a pass over instance chains to find the first one that unifies with the input type and then uses that.

I would propose to disallow unification under the following conditions that correspond to the example above:

  1. The input type has a type variable during unification
  2. The instance type has a type variable
  3. An instance in an instance chain, if passed in as an input, could unify with the actual input if the input were part of the instance chain.

Concretely, in the example above, that would mean the following:

  1. The input type has a type variable UncX i during unficiation.
  2. The instance type (UnkX x) has a type variable.
  3. An instance in the instance chain, (UnkX (UnkX Unk0)), if passed in as an input, could unify with UncX i if UncX i were part of the instance chain.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions