11

According to Command-Query Separation principle, as well as Thinking in Data and DDD with Clojure presentations one should separate side effects (modifying the world) from computations and decisions, so that it would be easier to understand and test both parts.

This leaves an unanswered question: where relatively to the boundary should we put "asking the world"? On the one hand, requesting data from external systems (like database, extental services' APIs etc) is not referentially transparent and thus should not sit together with pure computational and decision making code. On the other hand, it's problematic, or maybe impossible to tease them apart from computational part and pass it as an argument as because we may not know in advance which data we may need to request.

Alexey
  • 1,199
  • 1
  • 9
  • 18
  • 1
    That's where the concepts of callbacks comes in. If you don't know in advance what data may be needed, provide a callback to the computational code where it can specify what data it needs, and have the other layers do the actual fetching and providing. If it needs to be asynchronous, the callback could even specify another function to call with the fetched data when it has become available. – Marjan Venema Nov 02 '13 at 16:37
  • 1
    @MarjanVenema, this is the only option that comes to my mind as well. Just from the theoretical point of view: if the method, otherwise side-effect-free, invokes side-effectful callback it becomes side-effectful. Probably my issue here is that I assume that separation computation from side effects requires the computation to be referentially transparent. Though it's not necessary true. – Alexey Nov 02 '13 at 17:02
  • 1
    If that is your worry, your computation simply is not finegrained enough. You need to abstract out the decision making as to what other data/steps are needed. So split the full computation in steps based on where decisions are made as to what data is needed. Then have some kind of "director" that manages the workflow for the full computation: starting each step, getting back information from each step, using that to decide the next step and the data needed, starting a fetching process to get it and then passing the fetched data to the next step in the computation. – Marjan Venema Nov 02 '13 at 18:20

2 Answers2

1

On the other hand, it's problematic, or maybe impossible to tease them apart from computational part and pass it as an argument as because we may not know in advance which data we may need to request.

This is an instance where, as noted in the comments, passing in the ability to retrieve data (e.g., first-class function, an object that implements an interface, etc.) provides a convenient mechanism for isolating side effects.

A higher-order function whose body is pure has unfixed purity: http://books.google.com/books?id=Yb8azEfnDYgC&pg=PA143#v=onepage&q&f=false

I've written about this, calling this type of function a potentially-pure function: http://adamjonrichardson.com/2014/01/13/potentially-pure-functions/

If you combine a potentially-pure function with fall-through functions (which lack branching constructs and do as little as possible), a combination I call isolation sets, you can isolate side effects quite effectively and create very testable code: http://adamjonrichardson.com/2014/01/15/isolating-side-effects-using-isolation-sets/

Adam
  • 111
  • 3
0

You store the result in the class, this seems a bit strange at first, but does result in simpler code. e.g. no temporary variables in caller.

class database_querier
    feature -- queries
        was_previous_query_ok : boolean is
            do
                Result = …
            end

        previous_query_result : string is 
            requires
                was_previous_query_ok
            do
                Result = query_result
            end

    feature -- commands
        query_db (…) is
            do
                …
                query_result = bla
            end

    feature {none} --data
        query_result : string
ctrl-alt-delor
  • 570
  • 4
  • 9