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

时间:2014-06-10 07:59:05

标签: haskell sml ml higher-rank-types hindley-milner

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

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

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

我想象的场景是这样的:

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

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

我见过this,但如果我理解正确(...很可能不是),那实际上并不会阻止对资源的所有引用进行共享,只确保必须“关闭”对它的一个引用。

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

1 个答案:

答案 0 :(得分:2)

经过一些轰动之后,我认为 是可能的 - 或者至少足够接近它才能发挥作用 - 尽管看起来并不是很好看。 (我可能在这里走错了路,有知识的人请发表评论。)

使用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或声明可以分配给它的外部可变变量,从而处理大部分问题。但它仍然可以通过在"区域内创建的功能来关闭。阻止,并保持这种方式超过其所谓的关闭点; 可以导出这样的函数,并再次使用悬空资源引用,从而导致问题。

我们可以模仿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)  (* 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的角色;如果它上面提供的所有操作都返回monadic值而不是直接工作,它将构建一系列延迟操作,只能通过返回run来执行。这会阻止闭包在块之外保留r副本的能力,因为它们实际上永远无法执行op链,无法返回run,因此无法访问它无论如何。

两次调用T.run将重复使用相同的"键"类型,这意味着它不等同于嵌套的forall,但如果无法在两个单独的调用之间共享r,则不应该有所作为;没有 - 如果无法返回,则无法在外部分配,任何封闭都无法运行在其上运行的代码。至少,我是这么认为的。