Version 5 (modified by guest, 2 years ago) (diff)

afterthought re Proxy types vs Kinds

DORF -- comparison to SORF (and to TDNR)

This is a brief point-by-point comparison to

I'll only dwell on where the DORF proposal differs.

The 'Base Design' is very similar: a library class Has, methods get/set, with an instance generated for each declared record/field. Field selection (either prefix per H98 or dot notation) is desugared to a call to the get method.

The String type parameter to Has, and Scope control

DORF uses a Proxy type for the type/kind-level 'peg' on which to hang the instances. Using a Proxy is a superficial difference, and mostly for practical reasons -- I was building a proptotype in 7.2.1 (which doesn't have user-definable or String Kinds).

There is, however, a significant difference on scoping/namespacing: the Proxy type (being a type) is bound by the usual module scoping, export/import and module qualification, etc. Are (String) Kinds bound by module scope?

The module namespacing in DORF is deliberate, and for exactly the same reason as namespacing in general: a different developer in a different module might 'accidentally' create a clashing name which is unrelated (that is, a name for a field or a record or both). As far as DORF is concerned, these are different names, so need the module name qualification.

In contrast: "The [SORF] proposal ... doesn't allow label names to be scoped: if one module internally uses "field" as a label name then another module can break the abstraction by using the same string "field"." SPJ goes on to discuss a work-round which he sees as "ugly", and concludes "we need scoped instances". DORF does not need any innovations around instances or type inference.

Wild afterthought:

  • DORF does need to apply module control/representation hiding to the field selector function.
  • But if that's controlled, perhaps it doesn't need to control the Proxy type(?)
  • (In fact, the Proxy type is a bit of an annoyance, with a shadowy existence just out of sight from the application programmer, except for the need to export/import it.)
  • Perhaps we could use Kinds instead, and take advantage of the inability to control their scope:
    • Kinds could be just used, not declared.
    • They could be String Kinds or some more abstract/compact/hidden representation.

Should get have a Proxy argument? (and should set ?)

"This choice does not make a major difference either way." [SPJ]. DORF likewise doesn't care. The prototype does use a Proxy argument, because it's implemented using types not Kinds, and GHC doesn't (yet) have type application.

Higher Rank Types and Type Functions

DORF follows SORF in using a "functional-dependency-like mechanism (but using equalities) " to manage the type inference for Has/get/set.

Virtual record selectors

Are a valuable feature, and particularly valuable because they 'look' just like record field selectors. DORF supports them as ordinary (overloaded) functions, defined in terms of field selectors (so that their types are inferred to need the records/fields).

Under DORF, virtual record selectors do not need Has instances, so don't run into the problem of what to do about the definition for set.

(Perhaps DORF has a contrary problem: what if we do want a set for a virtual field? -- for example to update a fullName into firstName and lastName. There's food for thought in Wadler's original paper that led to View Patterns "Views: A way for pattern matching to cohabit with data abstraction" [1987], 4. "Viewing a complex number in cartesian and polar coordinates". We may want our implementation of complex to be abstract. We provide (pseudo-) fields to select the coordinates. Then they're ever-so like methods for an (abstract) object. Also we want the (pseudo-) fields to be updatable, which means we need an instance for Has/get/set for fields that are not explicit in the record.)

Selectors as functions

We need to support H98-style record selectors. One of the design objectives for DORF is that field selectors continue to be functions, just as H98. (The difference is that DORF's are overloaded whereas H98's are monomorphic.) You can use dot notation with H98 field selectors without changing the H98 record definition.

Representation hiding

See the discussion under <No Mono Record Fields>. SORF has an issue, because (in effect) it needs to control export of instances. "Solving this issue is important" [SPJ]. DORF doesn't have this issue, because the field selector is just a function, so the module system can control its visibility, as usual.

Syntax - The dot notation

DORF pretty much agrees with SORF, including in keeping it simple by not supporting part-applied dot. Note that DORF does not rely on dot notation whatsoever -- it's purely syntactic sugar for function application. See <Dot as Postfix Function Apply>

Syntax - Syntactic sugar for Has

SPJ is spot-on. DORF follows SORF.

Record updates

DORF follows SORF in having a set method within class Has. It's definition and instances are tricky, to support changing the type [] of records/fields, and higher-ranked fields.

SPJ is "not very happy with any of this" (that is, any of the SORF approach to field update). DORF has implemented record update using (admitted) hacks. DORF has tried to hide the implementation behind syntactic sugar (for the Has constraint), to make available some design space for improvements. (Including possibly the Alternative proposal using Quasifunctor.)

To answer SPJ's question "what does e { x = True } mean if there are lots of "x" fields in scope? ": Under DORF, there is only a single "x" in scope(overloaded to appear in many record types). So

   e { x = True } :: r{ x :: Bool} => r

[] changing the type of records fields at update seems to be a required feature. DORF has implemented it to be compatible with H98, but this adds considerable complexity. We avoid the question of whether it's desirable.

DORF does not have the issues trying to support or avoid set for virtual fields, because those are not defined using Has.

(Source code that attempts to update via them, such as :

    myCust { fullName = "Fred Dagg" }

will get type failure:

    no instance Has Customer_NameAddress Proxy_fullName String

or earlier still (if we implement DORF using proxies):

    no type Proxy_fullName

Updating polymorphic/higher-ranked fields

The prototype for this proposal originally included a method of updating Higher-ranked fields.

It used an extra type function SetTy, with an extra type argument bound from the definition of method set:

    type family SetTy r fld t _a :: *        -- type of the argument to set into the record
        set :: fld -> (forall _a. SetTy r fld t _a) -> r -> SetResult r fld t
    type instance SetTy HR Proxy_rev t _a = [_a] -> [_a]
        set _ fn HR{..} = HR{rev = fn}        -- now works

SPJ has quickly reviewed the prototype:

"Your trick with SetTy to support update of polymorphic fields is, I belive, an (ingenious) hack that does not scale. I think it works only for fields that are quantified over one type variable with no constraints.
So, I think that update of polymorphic fields remains problematic. "

  • We could scale up the hack by providing arbitrarily many forall-bound type variables to SetTy (_a _b _c ...). How many is more than enough?)
  • But we can't support constraints over the type variables. (Because constraints can't appear on the RHS of a type function instance. Perhaps this could be hacked using Constraints Kinds when they mature? They'd have to be declared on the method set within Has.)
  • Note that the "(ingenious)" and unscalable "hack" appears only in compiler-generated code.

Is it a requirement to be able to update polymorphic fields? Is it sufficient to be able to update other (monomorphic) fields in records that also contain poly fields?

Relationship to Type Directed Name Resolution [TDNR]

DORF is in some ways a reversion to something closer to TDNR (especially dot notation "works with any function"). DORF, SORF and TDNR all use a Has constraint, generated from the record/field declaration.

Unlike TDNR, DORF has no special syntax to trigger name resolution. (DORF's dot postfix notation is just syntactic sugar for function application, and name 'resolution' is type-directed only in the sense of familiar instance resolution.)

But the crucial difference is that TDNR still treats multiple declarations of the same field name (in different record types) as being different names. DORF treats these as multiple instances of the same name.