Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"WellFounded" relation passes totality check, builds and hangs #4616

Open
michaeljklein opened this issue Dec 14, 2018 · 1 comment
Open

Comments

@michaeljklein
Copy link

I found a way to implement arbitrary coercion between types by using an infinite loop that type checks as a WellFounded relation.

Steps to Reproduce

» idris -v
1.3.1
||| WellFoundedLoop.idr
%default total

data FreeAccessible : {a:Type} -> (x:a) -> (y:a) -> Type where
  FreeAccess : {a:Type} -> {x:a} -> {y:a}
            -> Accessible FreeAccessible x
            -> FreeAccessible x y

implementation WellFounded (FreeAccessible {a}) where
  wellFounded x = Access (\y, (FreeAccess {x} {y} access') => access')

freeAccess : {a:Type} -> (x:a) -> (y:a) -> FreeAccessible {a} x y
freeAccess x y = FreeAccess {x} {y} (wellFounded x)

freeRec : {a:Type} -> (step : (x : a) -> ((y : a) -> b) -> b) -> a -> b
freeRec step =
  wfRec {rel=FreeAccessible} (\x, f => step x (\y => f y (freeAccess y x)))

coerce : {a:Type} -> {b:Type} -> a -> b
coerce = freeRec (\x, f => f x)

main : IO ()
main = coerce ()

To build:

idris --total --V2 WellFoundedLoop.idr -o WellFoundedLoop

Expected Behavior

The module should neither type check nor compile.

Observed Behavior

» idris --total --V2 WellFoundedLoop.idr -o WellFoundedLoop
Reading ./WellFoundedLoop.idr
Type checking ./WellFoundedLoop.idr
Totality checking ./WellFoundedLoop.idr
Universe checking ./WellFoundedLoop.idr
Writing IBC for: "./WellFoundedLoop.ibc"
Compiling Output.
Running Erasure Analysis
Defunctionalising
Inlining
Generating Code.
» time ./WellFoundedLoop
^C
./WellFoundedLoop  121.48s user 0.50s system 99% cpu 2:02.55 total
@michaeljklein
Copy link
Author

I was able to simplify the example:

%default total

||| Isomorphic to: `(a:Type) -> a -> a -> a -> ..`
data Foo : (a:Type) -> Type where
  MkFoo : (a -> Foo a) -> Foo a

||| `f` is derived to be smaller than `MkFoo f`
accFoo : {a:Type} -> {b:Type} -> a -> Foo a -> b
accFoo x (MkFoo f) = accFoo x (f x)

||| `Bar` is isomorphic to `Foo Bar`
data Bar : Type where
  MkBar : Foo Bar -> Bar

runBar : Bar -> Foo Bar
runBar (MkBar fooBar) = fooBar

fooBar : Foo Bar
fooBar = MkFoo runBar

bar : Bar
bar = MkBar fooBar

accBar : {a:Type} -> a
accBar = accFoo bar fooBar

The totality checker detects that certain variants of Foo are not strictly positive as expected:

data CoFoo : (a:Type) -> Type where
  MkCoFoo : (CoFoo a -> a) -> CoFoo a

data EndoFoo : (a:Type) -> Type where
  MkEndoFoo : (EndoFoo a -> EndoFoo a) -> EndoFoo a

But the following variant allows us to define barId, which causes the type-checker to enter an infinite loop and leak memory:

data FooId : (a:Type) -> Type where
  MkFooId : (a -> a) -> FooId a

runFooId : FooId a -> a -> a
runFooId (MkFooId f) = f

||| `BarId` is isomorphic to `BarId -> BarId`
data BarId : Type where
  MkBarId : FooId BarId -> BarId

runBarId : BarId -> FooId BarId
runBarId (MkBarId f) = f

fixBarId : (BarId -> BarId) -> BarId
fixBarId f = f (MkBar (MkFoo f))

barId : BarId
barId = fixBarId (\b => fixBarId (runFooId (runBarId b)))

I'm not sure BarId is the same bug: should I move it to another issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant