Zulip Chat Archive

Stream: lean4

Topic: Replicating ML-style modules with classes / structures


George Pîrlea (Aug 05 2025 at 10:10):

Hi. I am converting a FStar module to Lean and looking for advice on the best way of representing it.

The original module looks like this:

module Foo
val concrete_st : Type0
val app_op_t : eqtype
type op_t = pos & (nat & app_op_t)
val app (s:concrete_st) (o:op_t) : concrete_st
end Foo

In Lean terms op_t is an abbrev that depends on the fields of Foo.

Something like this does not work:

mutual
class Foo where
  concrete_st : Type
  app_op_t : Type
  app (s : concrete_st) (op : op_t) : concrete_st

abbrev Foo.op_t {self : Foo} := Nat × (Nat × self.app_op_t)
end
-- invalid mutual block: either all elements of the block must be
-- inductive/structure declarations, or they must all be definitions/theorems/abbrevs

The Foo class will have many other fields that depend on op_t, so it's cumbersome to inline the definition everywhere, and using a macro for this seems weird.

What's the best way to represent this in Lean?

Shreyas Srinivas (Aug 05 2025 at 11:27):

class Foo  where
  concrete_st : Type
  app_op_t : Type
  op_t := Nat × (Nat × app_op_t)
  app (s : concrete_st) (op : op_t) : concrete_st

Robin Arnez (Aug 05 2025 at 11:28):

No, that'd make op_t a field with a default value

Shreyas Srinivas (Aug 05 2025 at 11:28):

Isn't that the intent?

Robin Arnez (Aug 05 2025 at 11:29):

I thought it should be fixed to Nat x Nat x app_op_t?

Robin Arnez (Aug 05 2025 at 11:29):

I would've done

class Foo where
  concrete_st : Type
  app_op_t : Type
  app' (s : concrete_st) (op : Nat × Nat × app_op_t) : concrete_st

abbrev Foo.op_t [Foo] := Nat × (Nat × app_op_t)
abbrev Foo.app [Foo] (s : concrete_st) (op : op_t) : concrete_st := app' s op

instead

Shreyas Srinivas (Aug 05 2025 at 11:30):

Then you must define it for an arbitrary instance

Robin Arnez (Aug 05 2025 at 11:31):

Hmm what do you mean?

Shreyas Srinivas (Aug 05 2025 at 11:31):

Right this makes more sense
Robin Arnez said:

I would've done

class Foo where
  concrete_st : Type
  app_op_t : Type
  app' (s : concrete_st) (op : Nat × Nat × app_op_t) : concrete_st

abbrev Foo.op_t [Foo] := Nat × (Nat × app_op_t)
abbrev Foo.app [Foo] (s : concrete_st) (op : op_t) : concrete_st := app' s op

instead

Shreyas Srinivas (Aug 05 2025 at 11:32):

The original example makes no sense to me because it looks like the you want to either have a default value for the type op_t or impose a constraint that op_t = Nat x (Nat x app_op_t)

Shreyas Srinivas (Aug 05 2025 at 11:33):

Otherwise, saying Foo.<field> = <val> makes no sense because a field belongs to an instance of the class

Sebastian Ullrich (Aug 05 2025 at 11:38):

A variant that is close to the original code is

class FooBase where
  concrete_st : Type
  app_op_t : Type

abbrev FooBase.op_t [FooBase] := Nat × (Nat × app_op_t)

open FooBase

class Foo extends FooBase where
  app (s : concrete_st) (op : op_t) : concrete_st

Of course this whole module style is not very prevalent in Lean but I am always interested to hear how it works out and if there are any remaining elaboration issues with it

George Pîrlea (Aug 05 2025 at 11:47):

That's very helpful. Thank you, everyone!

@Sebastian Ullrich's suggestion is the closest to what I intended. I'll try it out and see how far it gets me.

Shreyas Srinivas (Aug 05 2025 at 12:11):

Glad to hear it. @Robin Arnez : To describe my issue more succinctly, I think we don't have a concept of associated types like rust do we?


Last updated: Dec 20 2025 at 21:32 UTC