经过一番思考,我认为这是可能的 - 或者至少足够接近以便工作 - 尽管它看起来并不太美观。(我可能完全走了错路,请有知识的人发表评论。)
使用SML的生成数据类型和函数器,可以创建抽象幻影类型,这些类型在给定的词汇块之外无法被引用(我认为这是可能的):
datatype ('s, 'a) Res = ResC of 's
signature BLOCK = sig
type t
val f:('s, t) Res -> t
end
signature REGION = sig
type t
val run:unit -> t
end
functor Region(B:BLOCK) :> REGION where type t = B.t = struct
type t = B.t
datatype phan = Phan
fun run () = let
val ret = (print "opening region\n"; B.f(ResC Phan))
in print "closing region\n" ; ret end
end
structure T = Region(struct
type t = int
fun f resource = (
6
)
end)
;print (Int.toString(T.run()))
这可以防止程序简单地返回resource
或声明外部可变变量并将其分配给它,从而解决了大部分问题。但是,在“region”块内创建的函数仍然可以封闭它,并在其所需关闭点之后保留它;这样的函数可能会被导出,并再次使用悬空资源引用,导致问题。
我们可以像模仿ST
一样,通过强制该区域使用以虚类型为键的monad,防止闭包能够对resource
执行任何有用的操作:
signature RMONAD = sig
type ('s, 'a, 'r) m
val ret: ('s * 'r) -> 'a -> ('s, 'a, 'r) m
val bnd: ('s, 'a, 'r) m * ('a * 'r -> ('s, 'b, 'r) m) -> ('s, 'b, 'r) m
val get: 's -> ('s, 'a, 'r) m -> 'a * 'r
end
structure RMonad :> RMONAD = struct
type ('s, 'a, 'r) m = 's -> 's * 'a * 'r
fun ret (k, r) x = fn _ => (k, x, r)
fun bnd (m, f) = fn k => let
val (_, v, r) = m k
in f (v, r) k end
fun get k m = let val (_, v, r) = m k in (v, r) end
end
signature MBLOCK = sig
type t
val f:(t -> ('s, t, 'r) RMonad.m)
* ('r -> ('s, string, 'r) RMonad.m)
-> ('s, t, 'r) RMonad.m
end
signature MREGION = sig
type t
val run:unit -> t
end
functor MRegion(B:MBLOCK) :> MREGION where type t = B.t = struct
type t = B.t
datatype phan = Phan
fun run () = let
val (ret, res) = RMonad.get Phan (B.f(RMonad.ret(Phan, "RESOURCE"),
(fn r => RMonad.ret(Phan, "RESOURCE") r)))
in
print("closing " ^ res ^ "\n") ; ret
end
end
structure T = MRegion(struct
type t = int
fun f (return, toString) = let
val >>= = RMonad.bnd
infix >>=
in
return 6 >>= (fn(x, r) =>
toString r >>= (fn(s, r) => (
print ("received " ^ s ^ "\n");
return (x + 1)
)))
end
end)
;T.run()
这很混乱,但它展示了我的基本想法。
资源发挥 STRef
的作用;如果它上面提供的所有操作都返回一个单调值而不是直接工作,那么它将建立一个延迟操作链,只有通过返回到 run
才能执行。这样可以抵消闭包保留代码块外部的 r
副本的能力,因为它们实际上永远不能执行操作链,无法返回到 run
,因此无法以任何方式访问它。
两次调用 T.run
将重复使用相同的“key”类型,这意味着这不等同于嵌套的 forall
,但如果没有办法在两个独立的调用之间共享 r
,那就没有区别;而且确实没有-如果它不能被返回、不能在外部分配,并且任何闭包都无法运行处理它的代码。至少我是这么认为的。