范围管理 - 有状态IO Monad?

时间:2013-10-29 19:34:29

标签: java functional-programming functional-java

我正在使用函数式编程,尤其是Functional Java。我已经成功实现了我的IO Monad版本,我正在为我的核心编写IO动作。它基本上是将对象序列化为Xml文件(对象类型扩展了自定义XmlWritable接口)。

不幸的是,为了做到这一点,需要创建一个OutputStream实例和一个XmlSerializer实例。 OutputStream的范围比XmlSerializer更宽,这意味着我能看到能够正确处理IO monad中的两个生命周期的唯一方法是在元组中随身携带它们,在使用XmlSerializer编写后关闭OutputStream

这会导致繁重而丑陋的代码(Java 6绝对不是最好的代码):

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) {
                [...]
            }
        };
    }

为什么在函数式编程中处理我的用例有更多的惯用法吗?

潜伏着,我发现了State Monad ......但是如果我在功能Java的IO Monad上应用State Monad,我会害怕看到它会发生什么。

2 个答案:

答案 0 :(得分:2)

我实际上从Functional-Java的DB combinators中获得了很大的灵感来解决类似的问题。我从这个模式中创建了自己的“XML组合器”(以及更多),因此值得学习。

您可能会发现Google群组中的this discussion有用。

编辑 - 回复评论:

按照代码:
注意如何使用StateDb启动新连接,看看你有几个选项来启动连接,一个最终提交,一个最终回滚。这些只是你可以随身携带计算的两个例子。基本上,您bind(一个简单的modaic绑定)的每个计算都可能带有信息。

这是我在上面的讨论中给出的一个例子:

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();
}}}

因此,在此示例中,您可以将额外信息(即sql查询)作为参数传递给绑定的函数。你也可以为runStatement提供更多参数。

将它们放在一起,你会得到类似的东西:

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

希望这有帮助!

答案 1 :(得分:0)

这是我想出的。反馈非常感谢。 我按照上面的答案,从linked讨论中获取灵感:

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

    }
}