grafting functional support on top of an imperative language · 2008-04-04 · ideal...

37
Grafting Functional Support on Top of an Imperative Language How D 2.0 implements immutability and functional purity Andrei Alexandrescu Grafting Functional Support on Top of an Imperative Language – p. 1

Upload: others

Post on 14-Jul-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Grafting Functional Support onTop of an Imperative LanguageHow D 2.0 implements immutability and

functional purity

Andrei Alexandrescu

Grafting Functional Support on Top of an Imperative Language – p. 1

Page 2: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Overview of D 2.0

Systems-level programming language

Memory model similar to C (pointers!)

As convenient as a scripting language

Offers a well-defined machine-checkablesubset that is memory-safe

Powerful generics

Today: D 2.0 offers a pure functional subset

Grafting Functional Support on Top of an Imperative Language – p. 2

Page 3: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Why Functional Programming (FP)?

Increased modularityA part of a program cannot mess another

Easy debuggingThe call stack contains all context!

Safe Composition

Lazy evaluation offers iterators that neverinvalidate

Automatic concurrencyImmutable sharing is never contentious

Grafting Functional Support on Top of an Imperative Language – p. 3

Page 4: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Why Functional Programming (FP)?

Increased modularityA part of a program cannot mess another

Easy debuggingThe call stack contains all context!

Safe Composition

Lazy evaluation offers iterators that neverinvalidate

Automatic concurrencyImmutable sharing is never contentious

Grafting Functional Support on Top of an Imperative Language – p. 3

Page 5: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Why is FP Difficult?

The three “no”s of Functional Programming:

No mutable state

No side effects

No flow of control

Grafting Functional Support on Top of an Imperative Language – p. 4

Page 6: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Why Imperative Programming?

State makes things easy in many applicationsDatabases, persistence. . .

Fact: many algorithms are specified in termsof mutable state

Side effects are usefulInput/output, files, networking

I know FP has solutions to all of the above

Just saying that Some Bad People claimmutable state is easier and simpler for certainthings

Grafting Functional Support on Top of an Imperative Language – p. 5

Page 7: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Mixing the Two

Ideal language—allow:FP-style programming in parts of aprogram best suited for FPImperative programming for the rest

Programmer controls the ratio

Language statically rules out nonsensical ordangerous mixes of the two styles

The D 2.0 language implements such a mix

Grafting Functional Support on Top of an Imperative Language – p. 6

Page 8: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Challenges in Mixing FP and !FP

How to ensure that the procedural part doesnot modify the data of the functional part?

Complete isolation is not the answer!We want the two realms to communicatecomplex structures to each other

It’s not a simple matter of copying!Indirection, aliasing mess things up

Grafting Functional Support on Top of an Imperative Language – p. 7

Page 9: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Challenges in Mixing FP and !FP (II)

How to ensure that an FP function never callsa !FP function?

If it could, FP functions would have sideeffects!

How to ensure that a !FP thread doesn’tmess with the state of an FP call?

How to typecheck FP functions?What are the minimum applicablerestrictions?

Grafting Functional Support on Top of an Imperative Language – p. 8

Page 10: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Immutable State

Grafting Functional Support on Top of an Imperative Language – p. 9

Page 11: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

A C++-like const?

Here’s an idea:Use the const qualifier for all FP dataSelectively use non-const data otherwiseThe const qualifier is passed along withthe type, so no risk of “forgetting” itconst data cannot be assigned

Problem solved!

Grafting Functional Support on Top of an Imperative Language – p. 10

Page 12: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

A C++-like const?

const won’t work because:It is shallowProtects only the direct fieldsIndirectly-accessed data remains mutable

It suffers from aliasing with non-const dataThere may be mutable pointers andreferences aliasing with const pointersand referencesThat happens even if the shallow-nesswere solved!

Grafting Functional Support on Top of an Imperative Language – p. 11

Page 13: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

C++ const is shallow

struct Node { int value; Node* next; ... }

const Node* n1 = new Node;

Node* n2 = n1.next; // fine

We want to enforce that anything reachablefrom a const Node is also const

Otherwise a FP function cannot accept datain confidence that it can’t be changed

Grafting Functional Support on Top of an Imperative Language – p. 12

Page 14: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

C++ const is shallow

Transitivity via const functions:

class Node {

Node* next_;

public:

Node* next() { return next_; }

const Node* next() const

{ return next_; }

}

Hand-written contracts, not statically checkable

Grafting Functional Support on Top of an Imperative Language – p. 13

Page 15: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Defining a transitive const

Type constructor, notation: invariant(T)

Rule 0: Can’t assign to invariant(T)

Rule 1: if T.field has type U, theninvariant(T).field has typeinvariant(U)

Rule 2: invariant(invariant(T)) ≡invariant(T)

Rule 3: T implicitly converts to and frominvariant(T) iff T refers no mutablememory

Grafting Functional Support on Top of an Imperative Language – p. 14

Page 16: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Example

struct Node { int value; Node* next; ... }

invariant(Node)* n1 = new invariant(Node);

Node* n2 = n1.next; // error!

invariant(Node)* n3 = n1.next; // fine

invariant(int) x = n1.value; // fine

int y = n1.value; // fine because

// int has no references

Grafting Functional Support on Top of an Imperative Language – p. 15

Page 17: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Expressiveness Problem

void print(invariant(Node)* n);

Node* n = new Node;

print(n); // error!

invariant is too strict

How to define a function that can printinvariant or mutable nodes?

Must either duplicate the body of print orrely on a cast

Grafting Functional Support on Top of an Imperative Language – p. 16

Page 18: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Defining const as the intermediary

Type constructor, notation: const(T)

Rule 0: Can’t assign to const(T)

Rule 1: if T.field has type U, thenconst(T).field has type const(U)

Rule 2: const(const(T)) ≡ const(T)

Rule 3: T and invariant(T) both implicitlyconvert to const(T)

Grafting Functional Support on Top of an Imperative Language – p. 17

Page 19: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Defining const as the intermediary

Type constructor, notation: const(T)

Rule 0: Can’t assign to const(T)

Rule 1: if T.field has type U, thenconst(T).field has type const(U)

Rule 2: const(const(T)) ≡ const(T)

Rule 3: T and invariant(T) both implicitlyconvert to const(T)

Grafting Functional Support on Top of an Imperative Language – p. 17

Page 20: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Folding Rules

Problem: weird types may appearconst(invariant(const(...T...)))

Define rules for folding combinations:

invariant(const(T)) ≡ invariant(T)

const(invariant(T)) ≡ invariant(T)

Grafting Functional Support on Top of an Imperative Language – p. 18

Page 21: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Intuition

const(T) x: I can’t modify x or anythingreachable from it

invariant(T) x: Nobody can modify x oranything reachable from it

invariant is great for FP code portions

Unqualified is great for !FP code portions

const is great for factoring code that acceptsdata from both worlds!

Grafting Functional Support on Top of an Imperative Language – p. 19

Page 22: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Initializing invariant data

During construction, an object’s fields mustbe assignable

Yet they can’t be non-invariant: somebodymay alias the address of a field to a pointer tomutable data!

Node.this() invariant {

value = 0;

global = &next;

}

Grafting Functional Support on Top of an Imperative Language – p. 20

Page 23: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Different Constructors

Unlike in C++, the invariant and regularconstructor cannot be shared

They typecheck very differently

The regular constructor is allowed to escapepointers to its members without restriction

Grafting Functional Support on Top of an Imperative Language – p. 21

Page 24: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

“Raw” and “Cooked” States

Typechecking is done in two stages

Initially this has type __raw(Node)

__raw is an internal qualifier not accessibleto user code

__raw fields can only be assigned to, that’s it

Once all members have been assigned to, thecompiler switches the object’s type toinvariant(Node), at which point it can benormally used

Grafting Functional Support on Top of an Imperative Language – p. 22

Page 25: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

“Raw” and “Cooked” States

Node.this() invariant {

// start as raw

value = 0;

next = null;

// shazam! object got cooked

// can be passed to functions

print("Done with creating node ", this);

}

Grafting Functional Support on Top of an Imperative Language – p. 23

Page 26: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Important Observation

Can you delete invariant data?

If so, all hell breaks loose

All functional languages rely on garbagecollection

D also offers garbage collection, withoutwhich FP in D would not be possible

Don’t do the crime if you can’t do the time!

Grafting Functional Support on Top of an Imperative Language – p. 24

Page 27: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Qualifier Summary

Transitive qualification is key

Two kinds: invariant and const

invariant: FP data—never, ever changed

const: just a view to possibly mutable data

Both are necessary to factor code workingwith FP and !FP

Grafting Functional Support on Top of an Imperative Language – p. 25

Page 28: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Pure Functions

Grafting Functional Support on Top of an Imperative Language – p. 26

Page 29: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Immutable Data Not Enough

int foonctional(invariant(Node) n) {

static int i = 42;

writeln(++i);

return n.value + i;

}

Looks like functional to you?

Signature suggests so!

Grafting Functional Support on Top of an Imperative Language – p. 27

Page 30: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Pure Functions

Need a pure storage class for functions:

int fun(invariant(Node) n) pure {

...

}

Challenge: typecheck the body of fun toensure it does not do any impure action

Grafting Functional Support on Top of an Imperative Language – p. 28

Page 31: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Pure Functions, Take 1

Disallow all calls to impure functions

Disallow all access to non-invariant data

By definition of invariant and pure, it iseasy to infer that the result only depends onthe inputs

Grafting Functional Support on Top of an Imperative Language – p. 29

Page 32: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Pure Functions, Take 1

int foonctional(invariant(Node) n) pure {

static int i = 42; // error!

writeln(++i); // error!

return n.value + i; // error!

}

Grafting Functional Support on Top of an Imperative Language – p. 30

Page 33: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Pure Functions, Take 1

int foonctional(invariant(Node) n) pure {

invariant(int) i = 42; // fine

writeln(i); // error!

return n.value + i; // fine

}

Grafting Functional Support on Top of an Imperative Language – p. 31

Page 34: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

An Unnecessary Restriction

int fun(invariant(Node) n) pure {

int i = 42; // error?

if (n.value) ++i; // error?

return n.value + i; // error?

}

Key observation: why disallow mutability ofautomatic state?

Result is still dependent solely on inputs!

Grafting Functional Support on Top of an Imperative Language – p. 32

Page 35: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Pure Functions, Take 2

Disallow all calls to impure functions

Allow access to invariant data

Allow automatic local mutable state

Disallow all other data access

By definition of invariant and pure, and byscoping of local state, we can infer that theresult only depends on the inputs

Grafting Functional Support on Top of an Imperative Language – p. 33

Page 36: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Yum

int fun(invariant(Node) n) pure {

int i = 42;

if (n.value) ++i;

int accum = 0;

for (i = 0; i != n.value; ++i) ++accum;

return n.value + i;

}

Got benefits of both FP and !FP worlds in oneplace!

Grafting Functional Support on Top of an Imperative Language – p. 34

Page 37: Grafting Functional Support on Top of an Imperative Language · 2008-04-04 · Ideal language—allow: FP-style programming in parts of a program best suited for FP Imperative programming

Conclusions

Invariant and mutable data can beharmoniously mixed in a unified typeframework

Transitive qualifiers are key

Pure functions can be modularly typechecked

Relaxed immutability inside a pure functionAllow !FP techniques to be used

It all rests on an efficient machine model!

Grafting Functional Support on Top of an Imperative Language – p. 35