datafix-0.0.0.1: Fixing data-flow problems

Copyright(c) Sebastian Graf 2018
LicenseISC
Maintainersgraf1337@gmail.com
Portabilityportable
Safe HaskellNone
LanguageHaskell2010

Datafix.Description

Description

Primitives for describing a data-flow problem in a declarative manner.

Import this module transitively through Datafix and get access to Datafix.Worklist for functions that compute solutions to your DataFlowProblems.

Synopsis

Documentation

newtype Node Source #

This is the type we use to index nodes in the data-flow graph.

The connection between syntactic things (e.g. Ids) and Nodes is made implicitly in code in analysis templates through an appropriate allocation mechanism as in NodeAllocator.

Constructors

Node 

Fields

Instances

Eq Node Source # 

Methods

(==) :: Node -> Node -> Bool #

(/=) :: Node -> Node -> Bool #

Ord Node Source # 

Methods

compare :: Node -> Node -> Ordering #

(<) :: Node -> Node -> Bool #

(<=) :: Node -> Node -> Bool #

(>) :: Node -> Node -> Bool #

(>=) :: Node -> Node -> Bool #

max :: Node -> Node -> Node #

min :: Node -> Node -> Node #

Show Node Source # 

Methods

showsPrec :: Int -> Node -> ShowS #

show :: Node -> String #

showList :: [Node] -> ShowS #

type LiftedFunc domain m = Arrows (ParamTypes domain) (m (ReturnType domain)) Source #

Data-flow problems denote Nodes in the data-flow graph by monotone transfer functions.

This type alias alone carries no semantic meaning. However, it is instructive to see some examples of how this alias reduces to a normal form:

  LiftedFunc Int m ~ m Int
  LiftedFunc (Bool -> Int) m ~ Bool -> m Int
  LiftedFunc (a -> b -> Int) m ~ a -> b -> m Int
  LiftedFunc (a -> b -> c -> Int) m ~ a -> b -> c -> m Int

m will generally be an instance of MonadDependency and the type alias effectively wraps m around domain's return type. The result is a function that produces its return value while potentially triggering side-effects in m, which amounts to depending on LiftedFuncs of other Nodes for the MonadDependency case.

type ChangeDetector domain = Arrows (ParamTypes domain) (ReturnType domain -> ReturnType domain -> Bool) Source #

A function that checks points of some function with type domain for changes. If this returns True, the point of the function is assumed to have changed.

An example is worth a thousand words, especially because of the type-level hackery:

>>> cd = (\a b -> even a /= even b) :: ChangeDetector Int

This checks the parity for changes in the abstract domain of integers. Integers of the same parity are considered unchanged.

>>> cd 4 5
True
>>> cd 7 13
False

Now a (quite bogus) pointwise example:

>>> cd = (\x fx gx -> x + abs fx /= x + abs gx) :: ChangeDetector (Int -> Int)
>>> cd 1 (-1) 1
False
>>> cd 15 1 2
True
>>> cd 13 35 (-35)
False

This would consider functions id and negate unchanged, so the sequence iterate negate :: Int -> Int would be regarded immediately as convergent:

>>> f x = iterate negate x !! 0
>>> let g x = iterate negate x !! 1
>>> cd 123 (f 123) (g 123)
False

data DataFlowProblem m Source #

Models a data-flow problem, where each Node is mapped to its denoting LiftedFunc and a means to detect when the iterated transfer function reached a fixed-point through a ChangeDetector.

Constructors

DFP 

Fields

class Monad m => MonadDependency m where Source #

A monad with a single impure primitive dependOn that expresses a dependency on a Node of a data-flow graph.

The associated Domain type is the abstract domain in which we denote Nodes.

Think of it like memoization on steroids. You can represent dynamic programs with this quite easily:

>>> :{
  transferFib :: forall m . (MonadDependency m, Domain m ~ Int) => Node -> LiftedFunc Int m
  transferFib (Node 0) = return 0
  transferFib (Node 1) = return 1
  transferFib (Node n) = (+) <$> dependOn @m (Node (n-1)) <*> dependOn @m (Node (n-2))
  -- sparing the negative n error case
:}

We can construct a description of a DataFlowProblem with this transferFib function:

>>> :{
  dataFlowProblem :: forall m . (MonadDependency m, Domain m ~ Int) => DataFlowProblem m
  dataFlowProblem = DFP transferFib (const (eqChangeDetector @(Domain m)))
:}

We regard the ordinary fib function a solution to the recurrence modeled by transferFib:

>>> :{
  fib :: Int -> Int
  fib 0 = 0
  fib 1 = 1
  fib n = fib (n-1) + fib (n - 2)
:}

E.g., under the assumption of fib being total (which is true on the domain of natural numbers), it computes the same results as the least fixed-point of the series of iterations of the transfer function transferFib.

Ostensibly, the nth iteration of transferFib substitutes each dependOn with transferFib repeatedly for n times and finally substitutes all remaining dependOns with a call to error.

Computing a solution by fixed-point iteration in a declarative manner is the purpose of this library. There potentially are different approaches to computing a solution, but in Datafix.Worklist we offer an approach based on a worklist algorithm, trying to find a smart order in which nodes in the data-flow graph are reiterated.

The concrete MonadDependency depends on the solution algorithm, which is in fact the reason why there is no satisfying data type in this module: We are only concerned with declaring data-flow problems here.

The distinguishing feature of data-flow graphs is that they are not necessarily acyclic (data-flow graphs of dynamic programs always are!), but under certain conditions even have solutions when there are cycles.

Cycles occur commonly in data-flow problems of static analyses for programming languages, introduced through loops or recursive functions. Thus, this library mostly aims at making the life of compiler writers easier.

Minimal complete definition

dependOn

Associated Types

type Domain m :: * Source #

The abstract domain in which Nodes of the data-flow graph are denoted. When this is a synonym for a function, then all functions of this domain are assumed to be monotone wrt. the (at least) partial order of all occuring types!

If you can't guarantee monotonicity, try to pull non-monotone arguments into Nodes.

Methods

dependOn :: Node -> LiftedFunc (Domain m) m Source #

Expresses a dependency on a node of the data-flow graph, thus introducing a way of trackable recursion. That's similar to how you would use fix to abstract over recursion.

Instances

(Datafixable (DependencyM graph domain), GraphRef graph) => MonadDependency (DependencyM graph domain) Source #

This allows us to solve MonadDependency m => DataFlowProblem m descriptions with solveProblem. The Domain is extracted from a type parameter.

Associated Types

type Domain (DependencyM graph domain :: * -> *) :: * Source #

Methods

dependOn :: Node -> LiftedFunc (Domain (DependencyM graph domain)) (DependencyM graph domain) Source #

class (MonadDependency mdep, Monad mdat) => MonadDatafix mdep mdat | mdat -> mdep where Source #

Builds on MonadDependency by providing a way to track dependencies without explicit Node management. Essentially, this allows to specify a build plan for a DataFlowProblem through calls to datafix in analogy to fix or mfix.

Minimal complete definition

datafix

Methods

datafix :: ChangeDetector (Domain mdep) -> (LiftedFunc (Domain mdep) mdep -> mdat (a, LiftedFunc (Domain mdep) mdep)) -> mdat a Source #

This is the closest we can get to an actual fixed-point combinator.

We need to provide a ChangeDetector for detecting the fixed-point as well as a function to be iterated. In addition to returning a better approximation of itself in terms of itself, it can return an arbitrary value of type a. Because the iterated function might want to datafix additional times (think of nested let bindings), the return values are wrapped in mdat.

Finally, the arbitrary a value is returned, in analogy to a in mfix :: MonadFix m => (a -> m a) -> m a.

datafixEq :: forall mdep mdat a. MonadDatafix mdep mdat => Currying (ParamTypes (Domain mdep)) (ReturnType (Domain mdep) -> ReturnType (Domain mdep) -> Bool) => Eq (ReturnType (Domain mdep)) => (LiftedFunc (Domain mdep) mdep -> mdat (a, LiftedFunc (Domain mdep) mdep)) -> mdat a Source #

Shorthand that partially applies datafix to an eqChangeDetector.

eqChangeDetector :: forall domain. Currying (ParamTypes domain) (ReturnType domain -> ReturnType domain -> Bool) => Eq (ReturnType domain) => ChangeDetector domain Source #

A ChangeDetector that delegates to the Eq instance of the node values.

alwaysChangeDetector :: forall domain. Currying (ParamTypes domain) (ReturnType domain -> ReturnType domain -> Bool) => ChangeDetector domain Source #

A ChangeDetector that always returns True.

Use this when recomputing a node is cheaper than actually testing for the change. Beware of cycles in the resulting dependency graph, though!