wiki:Defaulting

Version 5 (modified by malcolm.wallace@…, 7 years ago) (diff)

Add two new proposals for defaulting

Defaulting

Ambiguous Types, and Defaults for Overloaded Numeric Operations in the Haskell 98 Report.

For removal:

  • Defaults are limited to certain classes. A tool like Hat, which transforms Haskell source, cannot transform the defaults, because there is no way make defaults apply to the transformed classes rather than the original ones.

For fixing:

  • Report specification when it comes to defaulting is impossible to implement when general recursive modules are allowed.
  • It should be specified that a group of mutually recursive modules must have exactly the same defaulting.

For replacement:

  • Perhaps require a default clause to name the class being defaulted over, as well as the type to choose.

Other issues:

  • A default clause applies only within the module containing the declaration. Defaults can be neither exported nor imported. Does anyone wish to propose import/export of defaults?
    • For import/export: easier to propagate user-defined class defaults throughout a project
    • Against import/export: a change in the imports of a module might silently change behavior
    • A compromise might be to allow defaults which will be inherited to be specified only in the module that defines a class, but groups of mutually recursive modules may override defaulting locally. this will avoid the import changing behavior problem and allow some sort of inheritence of defaults.

proposal 1

Allow defaulting clauses of the following form

default <classname> (type1,type2,type3,...)

The defaulting rule will simply choose the first unambiguous type that satisfies all the constrained classes listed in the default decls.

Classes without defaults will have the equivalent of an empty list of types, so defaulting will not occur.

It is important to specify "unambiguous", because in the case of

default A (Int, String, ())
default B (String, Int, ())

the only valid default for a type in both classes A and B should be (). This avoids making an arbitrary choice based on textual ordering of the default declaration clauses.

pro

  • very useful in interactive interpreter
  • less ad hoc than current method
  • overcomes the Hat transformation problem

con

  • can not exactly replicate behavior of existing defaulting mechanism, but can come close.
  • might hide errors, an optional warning on defaulting should be possible.

Proposal 2

Change the syntax of the defaulting clause from:

topdecl  ->  default ( type_1 , ... , type_n )

to

topdecl  ->  default ( tycls_1 => type_1 , ... , tycls_n => type_n )

Semantically, which type is chosen is determined by the class context at the choice point (after context simplification). If there is no unique choice, for instance because more than one class-with-a-default is mentioned in the context (and their default types are different), then it is a static error. Note that it is OK to have several classes in the context of the defaultable value, provided only one of them is declared to yield a default type, or if more than one yields a type, those types are the same. Choosing a default after context simplification means that no conflicts between super- and sub-classes can arise.

The current Haskell'98 default-default behaviour can be specified as:

default ( Num => Integer
        , Real => Integer
        , Enum => Integer
        , Integral => Integer
        , Fractional => Double
        , RealFrac => Double
        , Floating => Double
        , RealFloat => Double
        )

Pros

  • less ad hoc than current method
  • overcomes the Hat transformation problem
  • does not rely on textual ordering of decl
  • permits defaulting of user-defined classes, not just Prelude ones

Cons

  • not sure if this exactly captures the existing Haskell'98 module (but because defaults are currently limited to Prelude classes, it probably does).

Proposal 3

Orthogonal to the issue of how to specify defaults, is the question of whether they should be module-local, or if instead we should treat them like instance declarations - global in scope. One can also imagine permitting explicit lexical scoping, i.e. a local override of the defaults, purely within a single function definition.

Concrete proposal: a default decl can occur anywhere that a type signature would be valid, and has the same scope. However, because default decls cannot be named, all default topdecls must be unique throughout a program. By contrast, a local default decl can override one at an outer scope, but only within its own inner scope.

Pros

  • Arguably, when user-defined classes and types are involved, it is a lot clearer to make any necessary default decls once only, in a library, and just have them apply everywhere, rather than to require end-users to understand the issues and make their own default decls.
  • Having consistent defaults throughout a program is semantically nicer (but you still get the opportunity to override them locally if you _really_ need to - just like with operator fixities).

Cons

  • Changes the Haskell'98 behaviour (where defaults are module-local). Question: Do any real programs actually rely on the Haskell'98 spec here?