Zulip Chat Archive

Stream: lean4

Topic: Using FFI to communicate with a stateful C++ library


Abdalrhman M Mohamed (Dec 18 2023 at 00:03):

I am working on a project where I need to communicate with a C++ library that provides efficient implementation of some operations. One problem I am facing is that the library is stateful and I don't know how to capture that from the Lean side. For example, assuming we have the following API in the C++ side:

class Foo
{
 public:
  Foo() : changed(false) {}

  void doSomething() { changed = true; }

  bool isChanged() { return changed; }

 private:
  bool changed;
};

I would be tempted to have the following interface from the Lean side:

private opaque FooImpl : NonemptyType.{0}

def Foo : Type := FooImpl.type

instance : Nonempty Foo := FooImpl.property

@[extern "foo_mk"]
opaque Foo.mk : Foo

@[extern "foo_doSomething"]
opaque Foo.doSomething (f : Foo) : Unit

@[extern "foo_isChanged"]
opaque Foo.isChanged (f : Foo) : Bool

def test (f : Foo) : Bool :=
  let _ := f.doSomething
  f.isChanged

However, this breaks Lean's purity, since the Foo object is mutated by the doSomething function, but this is not reflected in the type. I am worried that can lead to inconsistencies and errors in the Lean code, so I tried to use monads to represent state. The idea is to use a type FooM α that represents a computation that can manipulate a Foo object and return a value of type α. Here is what I would like my interface to be:

@[extern "foo_doSomething"]
opaque Foo.doSomething : FooM Unit

@[extern "foo_isChanged"]
opaque Foo.isChanged : FooM Bool

@[extern "foo_run"]
opaque Foo.run [Inhabited α] (m : FooM α) : α

open Foo in
def test : Bool :=
  run do
    doSomething
    isChanged

However, I am not sure if this is the right approach, or if it works as expected. I have two main questions:

  • How can I implement the FooM monad and run function?
  • How can I pass and return the Foo object between the two languages?

I would appreciate any help or suggestion on how to use FFI with a stateful C++ library in Lean. Thank you for your time and attention.

P.S. It's cheap to create a new instance of Foo in the example above, but it's expensive to do so in my application. So, I have to avoid that.

Yury G. Kudryashov (Dec 18 2023 at 03:23):

How do other languages like Haskell deal with that?

Tomas Skrivan (Dec 18 2023 at 09:45):

In your example the function doSomething should return Foo then you are not breaking purity. To implement this correctly you need to know how Lean's memory management works and you can mutate only when you get a unique pointer to Foo otherwise you need to make a copy.

In more complicated scenarios the monadic approach, as you proposed, is a better solution. For example, IO monad defines an auxiliary object RealWorld that represents "the real world" and you pass it along in the state monad.

I have used this approach to communicate with Houdini(3d program like Blender), I created monad Sop with it's c++ implementation

Tomas Skrivan (Dec 18 2023 at 09:51):

Also have a look at this example which shows how to exchange class/struct between c++ and lean.


Last updated: Dec 20 2023 at 11:08 UTC