Closed madroach closed 5 years ago
Fuse the two transaction type right now (and split transaction and db arguments in the inner functions).
I'd pick this option, but what I'm actually aiming for is removing the old transaction interface completely. I just kept it for retro-compatibility.
Another delicate point is the encoding of the property we want. In particular, as it stands your cap type is not useful and the type of get could be simplified (see Txn.get). Do we want write-only transactions (even though lmdb doesn't really support them) ?
What I'm aiming for is this:
type 'a cap
val ro : [ `Read ] cap
val rw : [ `Read | `Write ] cap
Which will allow for subtyping, so that one may create ro transactions on rw environments or ro child-transactions to a rw parent …
The library is young and not used so much, we can afford to break compatibility, so if you think you have a good API with only the top level most general transactions and you can get rid of the other ones without removing functionalities, go for it.
Another thing you need to make sure is to be able to have top level database declarations that will not trigger the value restriction, as this would make the library quite hard to use for beginners.
Here's the PR without the inner Txn module.
I also renamed Cursor
→Crs
and labelled the ~crs
arguments. I did this just to make it more symmetric to the Txn
and S
signatures. I have no strong feelings about this. So if you like the long module name better or would rather drop the labels, tell so.
One small note on the use of labels (no need to fix it now, we can revisit it later): labels should always be placed leftmost. Since labels can only be swaped towards the right, placing them leftmost in the argument list ensure they can be placed anywhere.
labels can only be swaped towards the right
Could you elaborate this ? To me it seems like putting optional arguments right is more flexible:
# let f a ?(b=1) () = a+b;;
val f : int -> ?b:int -> unit -> int = <fun>
# let h ?(b=1) a = a+b;;
val h : ?b:int -> int -> int = <fun>
# f ~b:2 3 ();;
- : int = 5
# f 3 () ~b:2;;
- : int = 5
# f 3 ();;
- : int = 4
# (f 2) ~b:3 ();;
- : int = 5
# (h 2) ~b:3;;
Error: This expression has type int
This is not a function; it cannot be applied.
That behavior is due to the fact that the presence of absence of optional argument is resolved when the last argument is applied. In f
, it's ()
. In h
, it's a
. Add an extra argument to h
, and the behavior will be the same.
Also, I would always write h
instead of f
. Adding a unit argument at the end of a function is only relevant when it only has labeled arguments. Otherwise, the "main" argument should come last, after all the labels.
I'm afraid I still don't get your point. How is it advantageous to put (optional?) labels leftmost?
As far as I can see it only makes a difference for partial applications. In partial applications optional arguments will be eliminated if they are leftmost, but will not be eliminated when they are further right. I prefer the latter behaviour.
Labels are very finicky, and putting them leftmost avoids most of the usual warts with their application. You'll have to believe me on that one, it's a lesson acquired rather painfully quite a long time ago. Beside, the whole community agrees on that one...
This PR contains #9 and #10
Let transactions span the whole environment
Transactions do span the whole environment and can be used for manipulations of any database and ever for creating / deleting databases. But until now their use was limited to only databases created by the containing
Lmdb.S
module. To remove this limitation, I put aTxn
module at toplevel, which doesn't contain any database manipulating functions. The database manipulating functions (get
,put
, …) in theMake :S
functor get an optional?txn
argument, to which a transaction created by the toplevelTxn
module can be passed.Performance is not affected.