packages feed

static-0.1.0.0: static.cabal

Cabal-Version:       3.0
Name:                static
Version:             0.1.0.0
Synopsis:            Type-safe and interoperable static values and closures
Description:
  Serialise closures in a type-safe way that interoperates across binaries.

  This package is inspired by @distributed-static@ and GHC's /static pointers/
  in @GHC.StaticPtr@, which came out of the same research. However, we make
  some significantly-different design choices, described below.

  GHC made the design choice to focus on guaranteeing that static values could
  be passed between nodes if they were running the exact same binary, since
  they are indexed by 64-bit integers automatically-generated by the compiler.
  @distributed-static@ attempts to support the same source program compiled by
  different versions of GHC. As part of this effort to preserve stability, one
  must pass in a table ('RemoteTable') whose keys represent the stability, and
  whose values are resolved potentially differently across compiler versions.

  The need for the caller to pass in a 'RemoteTable' is seen as a liability, so
  two subsequent packages @distributed-closure@ and @static-closure@ take the
  opposite approach, ripping out the 'RemoteTable' but doubling down on GHC's
  choice to guarantee compatibility only across different processes running the
  exact same binary program. Their uses cases are focused around compute
  clusters and other forms of centralised distributed computing, where this is
  easy to achieve and not a problem.

  Sometimes security is cited as a reason to have this restriction, but this is
  a /bogus argument/. "Guarantee compatibility only across same binary" means
  "same binary => compatibility" whereas the bogus security argument depends on
  "compatibility => same binary", which is not true - anyone who analyses your
  binary will know which numbers to spoof, to convince your program via this
  interface that they are "running the same code". Guaranteeing "same binary"
  in an adversial setting is in fact extremely hard and cannot be achieved
  perfectly; in the real-world it can only be approximated, and should be done
  so via mechanisms designed for it, not via numbers that are slightly hard to
  brute force at best and trivial to find out at worst.

  This package makes the /opposite/ choice, intended for less restricted and
  more open distributed computing environments such as the internet and
  decentralised protocols. In these contexts, the requirement of running the
  exact same binary program is impossible to achieve in practise. Furthermore,
  we see it as an /advantage/ that code does not need to be exactly the same -
  for example, one can serialise a closure and its inputs, /upgrade your code/,
  then resume running the closure on the same deserialised input arguments but
  with a bugfixed closure. The necessity to pass in an explicit 'RemoteTable'
  (here simply called 'staticTab') is not a liability, but a useful tool to
  represent high-level compatibility and interoperability. Two nodes with the
  same keys in their 'staticTab's, know that they can talk to each other
  interoperably even if their implementations differ significantly. One node
  that wishes to talk to different nodes running different minor versions of
  the same protocol, could instantiate two different 'staticTab's with the same
  keys but different implementations, to handle behavioural nuances between the
  minor versions. In general, it's a useful bit of metadata to keep around in
  your program code, and can help you perform smooth upgrades of a
  non-centralised networking protocol more easily.

  There are also a few technical differences between this and
  @distributed-static@, some of which could be re-adopted there too:

  *   We use dependent-types and type-level programming to guarantee type
      safety, rather than 'Rank1Dynamic'. This enables us to store all possible
      closure types instead of just rank-1 polymorphic functions, including but
      not limited to: rank-n functions, functions with constraints, those using
      higher-kinded types, etc.

  *   Our serialisation typeclasses are designed to interoperate with many
      different serialisation frameworks. Instances for 'Data.Binary' and
      'Codec.Serialise' are provided here for convenience.

  *   We have additional Template Haskell splices that support creating static
      values from top-level definitions that must refer to other static values,
      whether they be recursive or mutually-recursive or neither. This is
      achieved using an implementation of @mfix@ for the @Q@ monad.

  We did /not/ implement the ability to compose static references. The main
  reason is that, in our view, the /purpose/ of static closures is to represent
  which top-level tasks to execute, and the inputs to execute it on. This is
  the /interface/ or /contract/ of this concept. /How you run/ the task is an
  implementation detail, and as discussed above, this might be different across
  different machines or as time passes and we upgrade the code. Therefore it
  makes no sense to serialise a representation of "task A is the composition of
  closure B and closure C", because it is irrelevant to the /interface/.

  If your /interface/ is actually "run arbitrary user-defined code" (e.g. in a
  VM or EDSL evaluator) then it would indeed make sense to support composition,
  but then you should define your own AST, evaluator, and serialiser for this;
  and pass the ASTs around as regular runtime values, not static values.
  Supporting arbitrary ASTs like this is outside of the scope of this library.

  Further, in Haskell there are many ways of applying values not just @(_ :: a
  -> b) (_ :: a) :: b@, e.g. with constraints, with a combination of static and
  non-static arguments, with type applications, and so on. Only the simple form
  @(_ :: a -> b) (_ :: a) :: b@ is likely to be interoperable across multiple
  languages. Supporting AST statics would therefore unnecessarily restrict how
  we can implement the behaviour of a static closure in our chosen language.

  See unit tests for example usage, e.g.
  <https://github.com/infinity0/hs-static/tree/master/test/Control/Static/UnitTests.hs UnitTests>
Homepage:            https://github.com/infinity0/hs-static
Bug-Reports:         https://github.com/infinity0/hs-static/issues
License:             GPL-3.0-or-later
License-File:        LICENSE.GPL-3
Author:              Ximin Luo
Maintainer:          infinity0@pwned.gg
Copyright:           2020 Ximin Luo
Category:            Control, Static, Closure
Tested-With:         GHC >= 8.8.3
Extra-Source-Files:  CHANGELOG.md

Source-Repository head
  Type: git
  Location: https://github.com/infinity0/hs-static

Flag dev
  Description: Set compile flags for development
  Default:     False
  Manual:      True

Common generic
  Default-Language: Haskell2010
  Build-Depends: base >= 4 && < 5,
  GHC-Options:
    -Wall
    -Wno-unused-matches
    -Wredundant-constraints
    -Wincomplete-record-updates
    -Wincomplete-uni-patterns
  if flag(dev)
    GHC-Options:
      -Werror
      -O2
    -- some optimisations cause memory leaks; switch on -O2 and profiling so we
    -- can detect this during development so it doesn't cause surprises later
    GHC-Prof-Options:
      -fprof-auto

Library
  Import: generic
  Build-Depends:
      constraints
    , text
    , singletons
    -- for serialisation
    , binary
    , bytestring
    , serialise
    -- Control.Static.TH
    , template-haskell
  HS-Source-Dirs: src
  Exposed-Modules:
      Control.Static
    , Control.Static.Common
    , Control.Static.Closure
    , Control.Static.Serialise
    , Control.Static.Static
    , Control.Static.TH

Test-Suite doctests
  Import: generic
  GHC-Options: -threaded
  Build-Depends:
      doctest
    , static
  HS-Source-Dirs: test
  Type: exitcode-stdio-1.0
  Main-Is: DocTests.hs

Test-Suite unit
  Import: generic
  GHC-Options: -threaded
  Build-Depends:
      tasty
    , tasty-quickcheck
    , tasty-hunit
    , checkers
    , static
    , lens
    , mtl
    , transformers
  HS-Source-Dirs: test
  Type: exitcode-stdio-1.0
  Main-Is: UnitTests.hs
  Other-Modules:
    Control.Static.UnitTests