Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there viable and type safe alternatives to the 1:1 type/type-class-instance relation?

This question originally arose during my work on a dynamic JS type validator that relies on dictionary passing style as a rather simple type class mechanism, but I think it also applies to Haskell or other languages having a type class mechanism.

At first I figured it wouldn't be too big a problem to allow several type class instances per type in a setting with dictionary passing style, because I have full control of the type class resolution. But the actual issue seems to maintain type safety not type class resolution, as I am going to demonstrate using the type validator. Since the code is type annotated it is language agnostic to some extend.

// please assume `Number` to be a non-polymorphic `Int` type

Sum.Monoid                          // Monoid<Number>
Prod.Monoid                         // Monoid<Number>
Function.Monoid                     // Monoid<b> -> Monoid<(a -> b)>
Function.Monoid(Prod.Monoid)        // Monoid<(a -> Number)>
Function.Monoid(Prod.Monoid).append // (a -> Number) -> (a -> Number) -> a -> Number

When you apply these types type safety is compromized, because it is possible to just write the following contrieved expression without the validator complaining:

Function.Monoid(Prod.Monoid)
  .append(inc) (inc) (Sum.Monoid.empty); // yields 1 ((0 + 1) * (0 + 1)) instead of 4 ((1 + 1) * (1 + 1))

In Haskell each instance has its own distinct type to prevent such nonsensical expressions. But having to convert from one type-wrapper to another can be tedious.

Is there a viable, type-safe alternative or is this the very reason why Haskell's designers chose the distinct-type-per-class-instance simplification?

like image 599
Iven Marquardt Avatar asked Jul 08 '21 15:07

Iven Marquardt


People also ask

What are the four things that a well structured class diagram should have?

Describing the static view of the system. Showing the collaboration among the elements of the static view. Describing the functionalities performed by the system. Construction of software applications using object oriented languages.

What is the relationship between classes called?

Association Association is a relationship that is established between two distinct classes through their objects.

Which of the following are true about the class diagram select one?

the statement which is true about class diagram is that it depicts the relationship between the classes.

How is type safety enforced in Java?

The Java language is designed to enforce type safety. Anything in Java happens inside an object and each object is an instance of a class. To implement the type safety enforcement, each object, before usage, needs to be allocated.

What is type-safe code?

(For this discussion, type safety specifically refers to memory type safety and should not be confused with type safety in a broader respect.) For example, type-safe code cannot read values from another object's private fields. Robin Milner provided the following slogan to describe type safety: Well-typed programs cannot "go wrong".

What are some examples of non-type safe operations in C++?

However, a number of very common operations are non-type-safe; for example, the usual way to print an integer is something like printf ("%d", 12), where the %d tells printf at run-time to expect an integer argument.

What is the difference between type safety and type safety?

Type-safe code accesses only the memory locations it is authorized to access. (For this discussion, type safety specifically refers to memory type safety and should not be confused with type safety in a broader respect.) For example, type-safe code cannot read values from another object's private fields.


2 Answers

Perhaps one possibility would be to have some sort of scoped instance mechanism. Sort of half-baked, but imagine writing something like this:

with Prod.Monoid, Function.Monoid {
    append(inc)(inc)(empty)
}

(Say with X, Y, Z {e} is short for with X {with Y {with Z {e}}}.) Presumably it would then be an error to introduce a second instance for a given class/type pair within that scope:

with Prod.Monoid, Function.Monoid {
    -- error: conflicting instances for Number
    --     currently in scope: Prod.Monoid
    --     proposed additional instance: Sum.Monoid
    append(inc)(inc)(with Sum.Monoid {empty})
}

But using different instances in different scopes would be allowed:

with Prod.Monoid, Function.Monoid {
    append(inc)(inc)
}(with Sum.Monoid {empty})

Although the term you proposed would still be possible to write like this, at least it would be explicit about where abstraction boundaries lay.

Making such a feature work correctly in the presence of polymorphism and higher-order functions seems like it could be... exciting. Possibly publishable-research-level exciting. I'd be interested to find out whether a focused attempt can be pushed all the way through.

like image 179
Daniel Wagner Avatar answered Oct 22 '22 11:10

Daniel Wagner


In some situations you can safely use a local explicit instance. In Haskell the reflection library can be used to create such a local instance. See the Reified Monoids for example.

There was also a paper about building that into the language and perhaps making it more usable and general, but I have not read it: Coherent Explicit Dictionary Application for Haskell by Thomas Winant and Dominique Devriese, here is a quote from their contributions section:

In this paper, we propose a new form of explicit dictionary instantiation that preserves coherence and that is safe with respect to global uniqueness of instances, but can be directly applied to common use cases.

like image 2
Noughtmare Avatar answered Oct 22 '22 12:10

Noughtmare