fsharp / fsharp/fslang-suggestions

Add Intersection Types

Open
#600 30 comments 40 reactions 0 assignees View on GitHub
area: type-system
Dominant language
No language data
Stars
373
Forks
21
PR merge metrics
No merged PRs in 30d

Description

# Add Intersection Types

I propose we add Intersection Types to the language.

## What are they?

Intersection types are conceptually similar to union types.

Whereas a union type may be either one of multiple sub-types, an intersection types is all of its sub-types combined.

Because of this, and because in .Net classes may only inherit from a single parent, intersection types are only really usefull with interfaces.

__Example:__

```F#

type IA =
abstract member DoA : unit -> unit
type IB =
abstract member DoB : unit -> unit

type TUnion = A of IA | B of IB
type TIntersect = IA & IB

// instances

let unionA = TUnion.A({new IA with member DoA() = ()})
let unionB = TUnion.B({new IB with member DoB() = ()})

let intersected = TIntersect ({new IA with member x.DoA() = ()
interface IB with member x.DoB() = ()})

// usages:

// with a union, I need to match, and can then use the value it actually is
match unionA with
| TUnion.A a -> a.DoA()
| TUnion.B b -> B.DoB()

// in contrast. the intersection type is both sub-types:
intersected.DoA()
intersected.DoB()
```

The existing way of approaching this problem in F# is ...

Most often, unsafe type casts are used at runtime. They can also be approximated as a tuple:

```F#
let aAndB =
{new IA with member x.DoA() = ()
interface IB with member x.DoB() = ()}
let tpl : IA * IB = (aAndB :> IA, aAndB :?> IB)

// usage
(fst tpl).DoA()
(snd tpl).DoB()
```

## Real-World Problems that motivated this

### Representing an Actor that handles multiple messages

I am using Akka.Net in F#.
Akka.Net itself is objectly typed, but I have written a small typed wrapper on top of it similar to [Akkling](https://github.com/Horusiath/Akkling).

In Akka, an actor may handle multiple unrelated message types.

To correctly encode this in the type system, I would like to be able to say

```F#
let t : (IActorRef & IActorRef) = ...
```

where A and B are the two message types it can handle.

Note that this _could_ also be encoded with _true union types_ (unlike the explicit unions F# uses):

```F#
let t : IActorRef<(A|B)> = ...
```

__Workaround:__

Currently I explicitly unwrap and rewrap the ActorRef with the different types, which is basically an unsafe runtime cast.

### Exposing only certain parts of a class

I have an ``ObservableCollection<'T>``.

I want to expose this instance as readonly, but also want to expose its CollectionChanged-event so that clients can re-query the data if it changes.

__My wish:__

```F#
type MyT() =
let collection = ObservableCollection(int)
member x.Data : IEnumerable & INotifyCollectionChanged = collection
```

With intersection types, I could expose only the enumerable (so that the consumer can read the data) and the event (so he gets notified), but he would not be able to change the data.

__Current workarounds:__

I can either expose the underlying ObservableCollection directly, and hope no-one changes it, or I need to expose two properties.

### Re-Engineered ``System.IO.Stream``

Currently Stream is an abstract base class which provides all possible methods. You need to do feature-checks at runtime (CanSeek),otherwise you get an exception.

Imagine we could re-implement Stream with intersection types:

You would have ``ISeekable``, ``IReadable``, ``IWritable``, ...

You need a stream where you need to seek and read? Easy, ``ISeekable & IReadable``.

__Workaround:__

You could create all permutations as interfaces, e.g. ``IReadableAndSeekable : IReadable, ISeekable``,but this has two issues:

* the possible combinations explode
* you can't use an object that implements both ``IReadable`` and ``ISeekable``,but not the combined marker-interface ``IReadableAndSeekable``

## Pros and Cons

__The advantages of making this adjustment to F# are ...__

Intersection Types are an established part of type theory and implemented in, for example, [scala](http://docs.scala-lang.org/tour/compound-types.html).
They enable further type-safety and replace runtime casts.

__The disadvantages of making this adjustment to F# are ...__

I don't know of a way to encode it in the CLR metadata, so it would be a F# only feature. Depending on the implementation, it would probably be erased and visible as ``object`` + Attributes.

## Extra information

The scala documentation of Compound Types: http://docs.scala-lang.org/tour/compound-types.html

The typescript documentation of Intersection Types: https://www.typescriptlang.org/docs/handbook/advanced-types.html (it is the first one described)

A similar C# proposal: https://github.com/dotnet/roslyn/issues/4586

Estimated cost (XS, S, M, L, XL, XXL): XL

Related suggestions: (put links to related suggestions here)

## Affidavit (please submit!)

Please tick this by placing a cross in the box:
* [x] This is not a question (e.g. like one you might ask on [stackoverflow](http://stackoverflow.com)) and I have searched stackoverflow for discussions of this issue
* [x] I have [searched both open and closed suggestions on this site](http://github.com/fsharp/fslang-suggestions/issues) and believe this is not a duplicate
* [x] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:
* [x] This is not a breaking change to the F# language design
* [ ] I or my company would be willing to help implement and/or test this

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.