编码SML中的rank-2多态性等价物

Leu*_*nko 7 haskell ml sml hindley-milner higher-rank-types

runST是一个Haskell函数,它通过类型静态地约束资源的可用生命周期.为此,它使用rank-2多态性.标准ML的简单类型系统仅提供秩-1多态性.

标准ML是否仍然可以使用类型将资源的生命周期约束到类似的最终结果?

此页面此页面演示了一些重构代码的方法,只需要更简单的类型.如果我理解正确,核心是将表达式包装起来,以便它被上下文中可能的观察所取代,这些观察是有限的.这种技术一般吗?可以将它,或相关编码,具有一些可使用(显然在签名不相同)runST,以防止从被包裹的表达逸出值的类型被观察?如果是这样,怎么样?

我想象的场景是这样的:

magicRunSTLikeThing (fn resource =>
    (* do things with resource *)
    (* return a value that most definitely doesn't contain resource *)
)
Run Code Online (Sandbox Code Playgroud)

...其中magic...提供了用户提供的代码无法以任何方式共享的资源.显然,这样一个简单的界面具有单个库函数是不可能的,但也许有各种层次的包装和手工内联和提取......?

我已经看到了这一点,但如果我理解正确(...很可能不是),那实际上并不会阻止对资源的所有引用进行共享,只能确保对它的一个引用必须"关闭".

基本上我想在SML中实现安全类型的显式(非推断的MLKit样式)区域.

Leu*_*nko 3

经过一番思考后,我认为这可能的——或者至少足够接近它可以工作——尽管它看起来不太漂亮。(我可能完全错了,请知情者评论。)

(我认为)可以使用 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()))
Run Code Online (Sandbox Code Playgroud)

这可以防止程序简单地返回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()
Run Code Online (Sandbox Code Playgroud)

(这是一团糟,但它表明了我的基本想法)

该资源扮演以下角色STRef:如果它提供的所有操作都返回一个单子值而不是直接工作,它将构建一个延迟操作链,只能通过返回来执行run。这抵消了闭包在块外保留副本的能力r,因为它们实际上永远无法执行操作链,无法返回run,因此无法以任何方式访问它。

调用T.run两次将重复使用相同的“key”类型,这意味着这并不等同于嵌套forall,但如果无法r在两个单独的调用之间共享,那么这应该不会产生影响;不存在 - 如果它不能返回,不能在外部分配,并且任何闭包都不能运行对其起作用的代码。至少,我是这么认为的。