经过一番摸索后,我认为这是可能的——或者至少足够接近它可以工作——尽管看起来不是很漂亮。(我可能在这里完全走错了路,有知识的人请评论。)
有可能(我认为)使用 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 = ( (* this function forms the body of the "region" *)
6
)
end)
;print (Int.toString(T.run()))
这可以防止程序简单地返回resource
或声明可以分配给它的外部可变变量,这可以解决大部分问题。但它仍然可以通过在“区域”块中创建的函数关闭,并在其假定的关闭点之后保持这种方式;此类函数可能会被导出并再次使用悬空资源引用,从而导致问题。
不过,我们可以模仿,并通过强制区域使用以幻像类型为键的 monad 来ST
阻止闭包做任何有用的事情: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) (* return *)
* ('r -> ('s, string, 'r) RMonad.m) (* operations on r *)
-> ('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
两次将重用相同的“键”类型,这意味着这不等同于嵌套,但如果无法在两个单独的调用之间forall
共享,那应该没有区别;r
没有 - 如果它不能被返回,不能被分配到外部,并且任何闭包都不能运行在它上面工作的代码。至少,我是这么认为的。