Hacker News new | past | comments | ask | show | jobs | submit
> My initial research indicates that the field of "static language that natively supports capabilities" is surprisingly uncovered and there may be a rich field there.

You want to look for white papers that talk about object-capability systems. It's a fairly old and well-trod area of research. The E programming language[1] was all about that, and it was pretty late in the game on this stuff.

You emphasize natively, but the problem is that's not really well defined. For static capabilities, you're just essentially asking for a suffciently strong module system with parameterized abstract data types. It's literally a subset of the grammar and what it's designed to express. Mark Miller (one of the creators of E) demonstrated that[2].

The knock on effect of that quality is that anything which fulfills that requirement natively supports capabilities. It's part of the grammar. Doesn't even have to be object-oriented. A hackjob demonstration of an SML filesystem library with a brand/mint object capability pattern:

brand.sig:

    signature BRAND =
    sig
        type token
    end

mint.sig:

    signature MINT =
    sig
        include BRAND
        val mint : unit -> token
    end

makebrand.fun:

    functor MakeBrand () =
    struct
        type token = unit ref
        fun mint () = ref ()
    end

filesystem.sig:

    signature FILESYSTEM =
    sig
        type token
        val readFile  : token -> string -> string
        val writeFile : token -> string -> string -> unit
    end

filesystem.fun:

    functor FileSystem (B : BRAND) :> FILESYSTEM where type token = B.token =
    struct
        type token = B.token

        fun readFile (_ : token) (path : string) : string =
            "contents of " ^ path

        fun writeFile (_ : token) (path : string) (_ : string) : unit =
            ()
    end

trusted_fs_setup.sml:

    local
        structure FileAuthority :> MINT = MakeBrand ()
    in
        structure FS :> FILESYSTEM = FileSystem (FileAuthority)
        val rootFileToken : FS.token = FileAuthority.mint ()
    end

trusted_fs.cm:

    Library
        signature FILESYSTEM
        structure FS
        val rootFileToken
    is
        brand.sig
        mint.sig
        makebrand.fun
        filesystem.sig
        filesystem.fun
        trusted_fs_setup.sml

Now for any given library using the trusted_filsystem library:

  val doc = FS.readFile rootFileToken "/etc/motd" (* Works fine *)
 
Delegation is function application:

  fun helper (t : FS.token) = FS.readFile t "log.txt"
  val log = helper rootFileToken  
And these all fail:

  val fake : FS.token = ref () (* Trying to forge a token *)
  val t = FileAuthority.mint () (* Trying to bypass the trusted kernel in trusted_fs_setup.sml by calling the mint *)
  
  (* Trying to self-issue authority by making our own brand and mint *)
  structure MyCap = MakeBrand ()
  val t : FS.token = MyCap.mint ()   (* type mismatch *)

What's nice about this is... it's just normal modular programming. It's a very natural grain. It's also completely compile-time, no runtime overhead.

You can also do a lot of this with phantom types, and it'd be much more terse and easier to handle dynamic capabilities and stuff like a capability algebra, but it ends up way less auditable and is easy to have subtle errors which defeats the point. Also compiler errors will be much more opaque. IMO needing to manually make wrappers for composite capabilities, or to handle dynamic capabilities, is the lesser of two evils. With higher order modules, those problems go away entirely.

[1] - https://en.wikipedia.org/wiki/E_(programming_language)

[2] - https://homepages.ecs.vuw.ac.nz/~kjx/papers/ARND2018.pdf