exterm / erlang-types

Demonstrating Erlang's type system
MIT License
0 stars 0 forks source link

Presentation: A Zero Cost Type System #1

Open exterm opened 6 years ago

exterm commented 6 years ago

Why static types

Type systems can be very different

Java

Map<Integer, Integer> memoized = new HashMap<Integer, Integer>();

Ruby

memoized = {}

Haskell

memoized = empty
-- or
memoized = empty :: Map Integer Integer

Erlang

Memoized = dict:new()
% or
-spec memoized() -> dict:dict(integer(), integer()).
memoized() -> dict:new().
exterm commented 6 years ago

This talk heavily inspired by https://learnyousomeerlang.com/dialyzer

exterm commented 6 years ago

Hindley-Milner type inference

and(true, true) -> true;
and(false,   _) -> false;
and(_,   false) -> false.

% inferred type

(bool(),bool()) -> bool()

Wrong!

TODO why is this wrong? And why is the Hindley-Milner example in Erlang? Did I insert the wrong example?

exterm commented 6 years ago

placeholder

exterm commented 6 years ago

placeholder

exterm commented 6 years ago

Subtyping for Erlang

Erlang types overlap, so Hindley-Milner is not applicable.

Haskell guys (Phil Wadler and Simon Marlow) developed a subtyping system for erlang, but...

and(true,true) -> true;
and(false,X)   -> false;
and(X,false)   -> false.

% inferred type:
(1, false) -> false | true

in order to ensure that the function can never fail it is necessary to restrict the second argument to being false only.

They tried to keep their type system simple at the cost of accuracy, but still try to ensure that the program can never fail.

Also,

very little existing code needed to be changed to get through the typechecker

Code needs to be changed to satisfy the type checker, even if it is correct.

exterm commented 6 years ago

Dialyzer

Success types for Erlang.

Our method is simple because we address the type inference problem directly using constraint-based type inference techniques and without imposing any restrictions on function uses which are error-free in the operational semantics of the language.

Our method is also [...] able to automatically infer accurate types [...] without requiring any type information or guidance by the user.

Starts with the assumption that the program is correct, i.e. all functions accept all types - or: all values are of the top type.

It then goes through the code to find type constraints, reducing the set of acceptable values.

↪️ $ make example-1

If it is unsure, Dialyzer will accept the program. Take a look at the inferred types if you're curious.

↪️ $ dialyzer example-2.erl ↪️ $ typer example-2.erl

exterm commented 6 years ago

And?

So back to the and function example from earlier. What types are inferred via subtyping?

↪️ $ typer and_fun.erl

exterm commented 6 years ago

Let's find a few bugs

↪️ $ dialyzer discrepancy_1.erl

Dialyzer infers types through multiple levels of the call stack with ease: ↪️ $ dialyzer discrepancy_2.erl

If the behavior is complex enough though, it gives up. In this case, it doesn't understand what's going on, so it's optimistic and will assume the code works. ↪️ $ dialyzer discrepancy_3.erl

exterm commented 6 years ago

Type definitions and function specs

Only using type inference is boring. Let's construct a complex type ourselves and use it!

↪️ $ dialyzer cards.erl

There is a bug in there, but we need to help dialyzer find it.

exterm commented 6 years ago

More broken contracts

↪️ $ dialyzer type_definitions.erl

What does dialyzer think is wrong here?

exterm commented 6 years ago

Fancy types

Singleton types are interesting because they blur the distinction between types and values. They let us find very advanced bugs:

↪️ $ typer fancy_types.erl ↪️ $ dialyzer fancy_types.erl

They can even be used to detect race conditions! Erlang has a builtin global key-value store. If we know the type of a key to be written to to be a singleton type, we know the exact key that is written to. So, dialyzer can throw an error if two processes write to the same key. Sometimes.

exterm commented 6 years ago

There are other kinds of errors that can be found because success typing is a form of subtyping. Because we're starting with the top type any(), the set of all values, and every time we apply a type constraint we reduce the set, resulting in a subset.

↪️ $ ghc implicit_enum.hs

↪️ $ dialyzer implicit_enum.erl

exterm commented 6 years ago

Bonus

Polymorphic types (AKA generics)

↪️ $ dialyzer zoo.erl

↪️ $ dialyzer safe_zoo.erl

exterm commented 6 years ago

Links

Paper on success types, introducing dialyzer and typer Erlang type spec docs Paper looking back at getting dialyzer widely adopted

Also, a great resource on erlang in general https://learnyousomeerlang.com

exterm commented 6 years ago

Appendix

Why is ruby not expressive?

Ruby will run programs without complaining that are semantically wrong. Every language does that. But in some languages, types can be used to be more specific about the intended behavior. This way, errors can be caught by the type checker.

exterm commented 5 years ago

use something like https://yhatt.github.io/marp/ to build slides? Or https://gitpitch.com .

exterm commented 2 years ago

To Do: