fsharp / fsharp/fslang-suggestions
Add Intersection Types
- 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.