Scope management - Stateful IO Monad?

441 Views Asked by At

I am playing with functional programming and in particular with Functional Java. I have implemented with success my version of the IO Monad and I am writing IO actions for my core. It is basically serializing objects to Xml files (the object type extends the custom XmlWritable interface).

Unfortunately, in order to do that, an instance of OutputStream AND one instance of XmlSerializer needs to be created. The scope of the OutputStream is wider than XmlSerializer's, which means that the only way I can see to be able to correctly handle both lifecycles within my IO monad is to carry both of them with me in a tuple, closing OutputStream after having written using XmlSerializer.

This leads to heavy and ugly code (Java 6 is definitely not the best for this):

public abstract class IO<R> {
    [...]
}

public class IOActions {

    public final F<String, IO<OutputStream>> openFileFn() {
        return new F<String, IO<OutputStream>>() {
            @Override
            public IO<OutputStream> f(String fileName) {
                [...]
            }
        };
    }

    /* This will be partially applied, encoding will be fixed */
    public static final F<OutputStream, IO<P2<OutputStream, XmlSerializer>>> initSerializer() {
        return new F<OutputStream, IO<P2<OutputStream, XmlSerializer>>>() {
            @Override
            public IO<P2<OutputStream, XmlSerializer>> f(OutputStream os) {
                XmlSerializer = new ...
                [...]
            }

        };
    }

    /* This will be partially applied as well */
    public static final F2<XmlWritable, P2<OutputStream, XmlSerializer>, IO<P2<OutputStream, XmlSerializer>>> writeObjectFn() {
        return new F2<XmlWritable, P2<OutputStream, XmlSerializer>, IO<P2<OutputStream, XmlSerializer>>>() {
            @Override
            public IO<P2<OutputStream, XmlSerializer>> f(XmlWritable object, P2<OutputStream, XmlSerializer> p) {
                [...]
            }
        };
    }

Is there a more idiomatic why to handle my use case in functional programming?

Lurking, I discovered the State Monad...but I am kind of scared to see what it is going to happen if I apply a State Monad on top of a IO Monad in Functional Java.

2

There are 2 best solutions below

0
On

Here is what I have come up with. Feedback is very appreciated. I followed the answer above, taking inspiration from the linked discussion:

public class IOXml<T extends XmlWritable> implements DataWriter<T>{

    private final XmlSerializer mXmlSerializer;
    private final Option<String> mXmlEncoding;
    private final IO<OutputStream> ioCreateStream;
    private final F<OutputStream, IO<Unit>> ioCloseStream;

    @Inject
    IOXml(IO<OutputStream> createStream, F<OutputStream, IO<Unit>> closeStream, XmlSerializer xmlSerializer, Option<String> xmlEncoding) {
        mXmlSerializer = xmlSerializer;
        mXmlEncoding = xmlEncoding;
        ioCreateStream = createStream;
        ioCloseStream = closeStream;
    }

    /**
     * Write a T object which is XmlWritable.

     * @param osAndSer The tuple containing OutputStream and XmlSerializer.
     * @param object The object to write.
     * @return IO monad object.
     */
    protected IO<Unit> writeObject(final T object) {
        return new IO<Unit>() {
            @Override
            public Unit performIO() throws IOException {
                object.writeXml(mXmlSerializer);
                return Unit.unit();
            }
        };
    }

    protected final F<Unit, IO<Unit>> writeObjectFn(final T object) {
        return new F<Unit, IO<Unit>>() {
            @Override
            public IO<Unit> f(Unit a) {
                return writeObject(object);
            }
        };
    }

    /**
     * Initialize the XmlSerializer before using it.
     * @param os An OutputStream.
     * @param encoding The encoding of the xml file.
     * @return An IO action returning nothing.
     */
    protected IO<Unit> initXml(final OutputStream os) {
        return new IO<Unit>() {
            @Override
            public Unit performIO() throws IOException {
                mXmlSerializer.setOutput(os, mXmlEncoding.toNull());
                mXmlSerializer.startDocument(mXmlEncoding.toNull(), true);
                return Unit.unit();
            }
        };
    }

    /**
     * Close the XmlSerializer after.
     * @return An IO action returning nothing.
     */
    protected IO<Unit> closeXml() {
        return new IO<Unit>() {
            @Override
            public Unit performIO() throws IOException {
                mXmlSerializer.endDocument();
                return Unit.unit();
            }
        };
    }

    protected final F<Unit, IO<Unit>> closeXmlFn() {
        return new F<Unit, IO<Unit>>() {
            @Override
            public IO<Unit> f(Unit a) {
                return closeXml();
            }
        };
    }

    @Override
    public void close() throws IOException {
        closeXml().performIO();
    }

    @Override
    public void write(T object) {
        throw new UnsupportedOperationException("Are you sure? IOXml is a functional class. Use the function returned by liftIO instead.");
    }

    /**
     * Curried function to write XML objects, given the object itself and an OutputStream.
     * @return The curried function.
     */
    protected F<OutputStream, F<T, IO<Unit>>> writeFn() {
        // returning the outer
        return new F<OutputStream, F<T, IO<Unit>>>() {
            @Override
            public F<T, IO<Unit>> f(final OutputStream os) {
                // Returning the inner
                return new F<T, IO<Unit>>() {
                    @Override
                    public IO<Unit> f(T object) {
                        return initXml(os).bind(writeObjectFn(object)).bind(closeXmlFn());
                    }
                };
            }
        };
    }

    @Override
    public IO<Unit> writeIO(final T object) {
        return IOImpl.bracket(ioCreateStream,                      // init
                       ioCloseStream,                              // close
                       Function.partialApply2(writeFn(), object)); // body

    }
}
2
On

I actually took great inspiration from Functional-Java's DB combinators to solve similar problems. I made my very own "XML combinators" (and more) from this pattern, so its worth learning.

You might find this discussion on google groups useful.

edit - replying to the comment:

follow the code:
notice how you start a new connection using the StateDb, see that you have a few options to start a connection, one that eventually commits, and one that eventually rollback. these are just two examples of things you can "carry" with the computation. Essentially, every computation that you bind (a plain modaic bind), could potentially carry information.

here is an example i gave in the discussion above:

DB<PreparedStatement> prepareStatement(final String sql) {
  return new DB<PreparedStatement>() {
     public PreparedStatement run(Connection c) throws SQLException {
        return c.prepareStatement(sql);
}}}

// for a query that a reader might perform, i might have a function like this:   
F<PreparedStatement, DB<ResultSet>> runStatement() {
   public DB<ResultSet> f(final PreparedStatement s) {
      return new DB<ResultSet>() {
        public ResultSet run (Connection c) throws SQLException {
           return s.executeQuery();
}}}

So in this example, you can pass extra information, namely the sql query as a parameter to the function that gets bound. you could just as well had more parameters to runStatement.

to put it all together, you get something like:

ResultSet rs = DbState.reader("conn-url").run(prepareStatement("select * from table").bind(runStatement());

Hope this helps!