Skip to content

GitLab

  • Menu
Projects Groups Snippets
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
  • Sign in / Register
  • GHC GHC
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Locked Files
  • Issues 4,831
    • Issues 4,831
    • List
    • Boards
    • Service Desk
    • Milestones
    • Iterations
  • Merge requests 447
    • Merge requests 447
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
    • Test Cases
  • Deployments
    • Deployments
    • Releases
  • Analytics
    • Analytics
    • CI/CD
    • Code review
    • Insights
    • Issue
    • Repository
    • Value stream
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • Glasgow Haskell Compiler
  • GHCGHC
  • Issues
  • #17765

Closed
Open
Created Jan 30, 2020 by Jakob Brünker@JakobBruenkerDeveloper

Documentation for FunctionalDependencies is misleading/incomplete (and their behavior confusing) wrt overlapping instances

Summary

The GHC user's guide mentions in 9.8.2.2.2. Adding functional dependencies, for class D a b | a -> b where ...:

The compiler, on the other hand, is responsible for ensuring that the set of instances that are in scope at any given point in the program is consistent with any declared dependencies. For example, the following pair of instance declarations cannot appear together in the same scope because they violate the dependency for D, even though either one on its own would be acceptable:

instance D Bool Int where ...
instance D Bool Char where ...

This leaves out what happens when polymorphic types are used in instances, and some of the behavior of functional dependencies in that case is not what I would expect judging by the documentation, but I suspect that they work as intended.

To illustrate:

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}

module FunDeps where

class C a b | a -> b where
  foo :: a -> b

-- It is unclear to me why I can write both of these instances together - it
-- seems as though this should violate the dependency
instance C a a where
  foo = id

instance C a () where
  foo = const ()

-- understandably not fine, due to Overlapping instances
-- ex0 = foo () :: ()

-- fine
ex1 = foo 'x' :: Char

-- also fine
-- However, the User's guide says "The notation `a -> b` [...] indicates that the `a`
-- parameter uniquely determines the `b` parameter, and might be read as “a determines b.”"
-- From this I would expect that an expression like `foo 'x'` has a uniquely determined
-- type, but evidently, this is not so, since it can have both type `Char` and `()`.
ex2 = foo 'x' :: ()

-- not fine, with confusing error message
-- Why does it expect the argument of `foo` to be of type `()` when, with
-- an annotation, it accepts a `Char` argument?
-- In fact, it looks like it selected the `C a a` instance and from it determined that
-- it needs to solve a `C Char ()` constraint, which seems paradoxical.
-- Naively I would expect GHC to talk about an ambiguous type variable, which
-- is what happens if the functional dependency is removed.
ex3 = foo 'x'

{-
FunDeps.hs:25:7: error:
    • Couldn't match type ‘Char’ with ‘()’
        arising from a functional dependency between:
          constraint ‘C Char ()’ arising from a use of ‘foo’
          instance ‘C a a’ at FunDeps.hs:9:10-14
    • In the expression: foo 'x'
      In an equation for ‘ex3’: ex3 = foo 'x'
   |
25 | ex3 = foo 'x'
   |       ^^^^^^^
-}

Another interesting aspect is that entering () :: C Bool () => () in GHCi will produce the exact same warning twice, though I don't know if that's expected behavior:

<interactive>:45:7: warning: [-Wsimplifiable-class-constraints]
    • The constraint ‘C Bool ()’ matches
        instance C a () -- Defined at FunDeps.hs:15:10
      This makes type inference for inner bindings fragile;
        either use MonoLocalBinds, or simplify it using the instance
    • In an expression type signature: C Bool () => ()
      In the expression: () :: C Bool () => ()
      In an equation for ‘it’: it = () :: C Bool () => ()

<interactive>:45:7: warning: [-Wsimplifiable-class-constraints]
    • The constraint ‘C Bool ()’ matches
        instance C a () -- Defined at FunDeps.hs:15:10
      This makes type inference for inner bindings fragile;
        either use MonoLocalBinds, or simplify it using the instance
    • In an expression type signature: C Bool () => ()
      In the expression: () :: C Bool () => ()
      In an equation for ‘it’: it = () :: C Bool () => ()

Environment

  • GHC version used: 8.11.0.20200127
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information
Assignee
Assign to
Time tracking