Skip to content

GitLab

  • Projects
  • Groups
  • Snippets
  • Help
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
  • Sign in / Register
GHC
GHC
  • Project overview
    • Project overview
    • Details
    • Activity
    • Releases
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Locked Files
  • Issues 4,320
    • Issues 4,320
    • List
    • Boards
    • Labels
    • Service Desk
    • Milestones
    • Iterations
  • Merge Requests 359
    • Merge Requests 359
  • Requirements
    • Requirements
    • List
  • CI / CD
    • CI / CD
    • Pipelines
    • Jobs
    • Schedules
  • Security & Compliance
    • Security & Compliance
    • Dependency List
    • License Compliance
  • Operations
    • Operations
    • Incidents
    • Environments
  • Analytics
    • Analytics
    • CI / CD
    • Code Review
    • Insights
    • Issue
    • Repository
    • Value Stream
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Collapse sidebar
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
  • Glasgow Haskell Compiler
  • GHCGHC
  • Issues
  • #19126

Closed
Open
Opened Dec 27, 2020 by Anthony Clayden@AntC3Reporter

Dubious FunDep behaviour, even for a 'textbook' example with non dodgy extensions

Summary

GHC's behaviour for a 'textbook' FunDep instance (without the known-to-be-dodgy extensions) is not as per the theory in the 'FunDeps via CHRs' 2006 paper.

In itself this is not problematic; there's an easy workaround; indeed FunDepers probably take it 'this is how GHC works'. But the workaround needs UndecidableInstances, which removes any confidence about confluence and termination across all instances in the module.

(I'm documenting this in context of a whirl of recent issues #18759, #18851 and ref this comment on a github proposal.)

Steps to reproduce

Consider this classic textbook FunDep, from the Jones 2000 paper

{-# LANGUAGE  MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts #-}

class Collects e ce  | ce -> e  where
  member :: e -> ce -> Bool
  -- etc

instance Eq e => Collects e [e]  where
  member = elem

x1 = member True [False, False, True]                   -- ===> True
x2 = member undefined ([] :: [Bool])                    -- ===> False

The FunDeps via CHRs paper [Section 4.2/Fig 2 translation rules] says that instance should be equivalent to this:

{-# LANGUAGE  UndecidableInstances, GADTs  #-}

class CollectF2 e ce  | ce -> e  where
  memberF2 :: e -> ce -> Bool

instance (a ~ e, Eq e) => CollectF2 a [e]  where
  memberF2 = elem

Using ~ constraint to explicitly apply the type improvement (needs GADTs), and bare tyvar a in the instance head as target for the improvement (needs UndecidableInstances).

Expected behavior

This instance and its non-confluent consequence should be rejected:

instance {-# OVERLAPPABLE #-} (a ~ ()) =>  Collects a [e]  where
  member _ _ = False

x3 = member ()   [False, False, True]                   -- ===> False

Compare:

instance {-# OVERLAPPABLE #-} (a ~ ()) =>  CollectF2 a [e]  where
  memberF2 _ _ = False

-- error:     Duplicate instance declarations:

Best practice for using FunDeps (see examples cited in the github comments) is to use instance heads in a style per CollectF2 with a bare fresh tyvar as the target. This needs UndecidableInstances even though the type improvement via ~ is perfectly decidable. That extension is module-wide, and allows exactly the non-confluence with that instance (a ~ ()) => Collects a [e] it was seeking to avoid. FunDepers must be disciplined in how they write instances.

Environment

  • GHC version used: 8.10.2

Optional:

  • Operating System: Windows 8
  • System Architecture:
Assignee
Assign to
None
Milestone
None
Assign milestone
Time tracking
None
Due date
None
Reference: ghc/ghc#19126