简体   繁体   中英

Updating values in Haskell

I am trying to apply constraints to a set of values using intervals as their range of possible values. I am having difficulty formulating an approach to how i would apply these constraints in Haskell.

Say I have a constraint of the form b = 10 and a constraint a = f(b,c) , clearly the first constraint will alter how the second one is applied however I am unsure of how to implement this in a functional language. In an imperative program we would alter the interval of b and use its new value in the second constraint.

Any help would be much appreciated, Ben

This question might be bordering on "too general", so here's a possibly "too general" answer, though I've tried to include some specifics (including a working example).

In a problem like this, a typical imperative approach might use a single data structure to (1) store the input constraints; (2) act as a live scratch space for processing as constraints are read; and (3) perform any final computations to produce the results.

In a Haskell implementation, it's usual to separate this into distinct steps:

  1. Build a pure data structure that represents the input constraint system, without a lot of complex processing.
  2. Walk the data structure using a state monad to carry along the "discoveries" as constraints are processed.
  3. Generate the final result, either by querying the final state or as a by-product as the data structure is walked, whichever is more convenient.

Haskell's lazy evaluation can help here. Even though the implementation of these steps is separated, the on-demand evaluation scheme means that the computations are interleaved in practice, as they would be with the imperative implementation, so the result is generated as the data structure is walked as the input constraints are loaded into the constraint system representation. However, the separate functional components are usually easier to construct and reason about.

As an over-simplified example, imagine you're implementing equality constraints between variables (ie, a set of constraints of the form x==y ). Here, for Step 1, a pure data structure representing the constraint system is pretty trivial:

type Name = String
data Constraint = Equals Name Name deriving (Show)    -- ^An equality constraint
type System = [Constraint]                            -- ^A constraint system

In a more realistic example, there might be multiple constraint types and/or you might choose to organize them in a map keyed by variables that are referenced by the constraint (whatever's convenient for processing). The key insight here is that the data structure is pure and immutable and doesn't reflect any serious computation on the constraints. It's mostly a direct representation of the input.

For Step 2, in this simple case, a reasonable state representation of the "knowledge" gained by learning the constraints is a set of equivalence classes. We can use a Map Name Name -- following a chain from a given name n to its final name n' gives a representative for the class, so if two names have chains that end with the same final name, they are equal:

type St = Map Name Name

Again, in a more realistic example, this state might incorporate a copy of the constraint system representation annotated with discoveries made as the system is processed. These annotations can be added via the usual sorts of functional manipulations of data structures; or, you might find it useful here to introduce a true mutable data structure and run everything in the ST or IO monad.

Anyway, to continue with Step 2 of our example, we can then imagine walking the constraint System to update this state. Here S is shorthand for State St , a state monad with our St state.

walk :: System -> S ()
walk = mapM_ learn
learn :: Constraint -> S ()
learn = ...   -- update state to reflect knowledge from constraint

Finally, for Step 3, we can define a function to read off the "solution" which in our case takes the form of a list of (Name,Name) pairs where ("x","u") indicates that variable "x" is equal to its representative "u" (and any other variable with that representative is also equal to "u" and "x"s). This could be done outside of the S monad, as a pure computation on the final state returned by walk , but I've done it within the S monad here.

solution :: S [(Name,Name)]
solution = do ns <- gets (Map.keys)
              zip ns <$> mapM rep ns

Here, the function rep :: Name -> S Name uses the state to follow a chain from a supplied name n to its representative n' .

The only missing part is the implementation of learn . Here it is all together in a full working example. I hope that gives you some ideas on how to get started

module Constraint where

import Control.Monad.State
import Data.List
import Data.Maybe (catMaybes)
import Data.Map (Map)
import qualified Data.Map as Map

type Name = String
data Constraint = Equals Name Name deriving (Show)    -- ^An equality constraint
type System = [Constraint]                            -- ^A constraint system

-- |State representing evolving computation as collection of
-- equivalence classes.  Each name `n` maps to smaller `n'` and
-- following chain leads to class representative.  A much more
-- efficient implementation is possible (see the "equivalence"
-- package, for example, which uses a mutable data structure).
type St = Map Name Name   -- ^Our state
type S = State St         -- ^Monad w/ our state
evalS = flip evalState Map.empty
runS  = flip runState Map.empty

-- |Get equivalence class representative
rep :: Name -> S Name
rep n = do
  s <- gets (Map.lookup n)
  case s of Just n' | n /= n' -> rep n'
                    | otherwise -> error "rep: cycle in chain"
            Nothing -> return n

-- |Set link in equivalence class chain
set :: Name -> Name -> S ()
set n n' = when (n /= n') $ modify (Map.insert n n')

-- |Learn a new constraint
learn :: Constraint -> S ()
learn (Equals x y) = do rx <- rep x
                        ry <- rep y
                        unify rx ry

-- |Unify equivalence classes for two names
unify :: Name -> Name -> S ()
unify n n' | n < n'    = unify n' n
           | otherwise = set n n'

-- |Walk a system of constraints, learning them as we go
walk :: System -> S ()
walk = mapM_ learn

-- |Generate solution from complete state
solution :: S [(Name,Name)]
solution = do ns <- gets (Map.keys)
              zip ns <$> mapM rep ns


test :: [(Name, Name)]
test = evalS $ do -- Step 1: a pure representation of the constraint system
                  let sys = [ Equals "x" "y"
                            , Equals "u" "v"
                            , Equals "v" "z"
                            , Equals "a" "b"
                            , Equals "x" "w"
                            ]
                  -- Step 2: walk the system, learning state as we go
                  walk sys
                  -- Step 3: produce the solution from the state
                  solution

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM