简体   繁体   中英

Data Type with cached calculations in functional programming

Given the following example code in OOP style, what is a good way to get this behavior in a purely functional style, eg in Haskell

class P{
  private A a;
  private B b;
 
  public P(A a'){
     this.a=a';
     this.b=null;
  }
 
 public P(B b'){
     this.a = null;
     this.b = b';
 }

 public A getA(){ 
    if(this.a==null){
       //code to calculate a from b
       this.a = result;
    }
   return this.a
 } 

 public B getB(){ 
    if(this.b==null){
       //code to calculate b from a
       this.b = result;
    }
   return this.b
 } }

There are two fields a and b and when constructing the object, I often only have access to either one, but the other one can be calculated from the other. An easy example for this would be polygons that are either defined as the convex hull of a list of points or as the intersection of a list of lines (and the higher-dimensional analog for polyhedrons). It can be quite expensive to calculate b from a and vice-versa, so I don't want to immediately do the calculation when creating the object and instead wait until it is actually needed.

One idea I had for this would be to have a record type

data P = {a:: Maybe A, b:: Maybe B} 

makePA :: A -> P 
-- ...


makePB :: B -> P 
-- ...

where the two makeP create a P from either A or B . Then whenever I actually need one of the fields I could make a get function, similar to the above that calculates the field if needed and then returns the new record with both fields no longer Nothing . But this seems overly clunky, as there's some calculation needed for creating a P (eg calculating the convex hull of points) and so the record can't just be created like one would usually do. Furthermore, I'd have to encapsulate each use of P in other functions by a call to the get functions just to make sure the value is actually calculated and then access the field, which I still have to make sure is Just A and not Nothing due to the type being Maybe A .

Is there a better way to solve this?

It's much easier. Simply include both the A and the B , neither of them optionally, and rely on lazyness – it takes care of all deciding-whether-one-of-the-values-needs-to-be-calculated automatically.

data P = {a::A, b::B} 

calculateBfromA :: A -> B
calculateAfromB :: B -> A

makePA :: A -> P
makePA a' = P a' (calculateBfromA a')

makePB :: B -> P
makePB b' = P (calculateAfromB b') b'

You could say, Haskell actually has null values too like Java does, but they are always associated with a method to compute a proper value of the correct type. Maybe is for null values that actually have a denotional meaning, but in your example they only have an operational meaning, which Haskell can abstract away.

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