---
title: "Go-Like Interfaces and Rust-Like Traits on S7"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Go-Like Interfaces and Rust-Like Traits on S7}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

```{r setup}
library(S7)
library(s7contract)
```

## Introduction

`s7contract` is a small experiment: can interface and trait ideas be expressed
on top of S7 without replacing S7's method system?  The package answers yes, but
with an important constraint.  S7 still owns dispatch.  `s7contract` only records
and checks contracts around ordinary S7 generics.

The most natural layer is a Go-like structural interface.  In S7, operations are
already ordinary functions such as `draw(x)` or `area(x)`, and methods are
registered for classes.  A structural interface can therefore be just a named set
of required generics.

A Rust-like trait is also possible, but it needs an explicit registry.  That
extra machinery is useful for default methods and associated metadata, but it is
less native to S7 because R does not have Rust's compile-time trait bounds,
coherence rules, or associated type checker.

## Background

S7 is a functional object-oriented system: methods belong to generic functions,
not to objects.  The call is `generic(object, ...)`, not `object$generic(...)`.
That makes S7 close in spirit to protocols defined by behavior.

Go interfaces are structural: a basic interface describes required methods, and
a type satisfies the interface when it has those methods.  Go style also favors
small interfaces defined at the point of use: do not define an interface beside a
single implementation merely to make that implementation conform.  In S7 terms,
define classes, generics, and methods where the behavior lives, then let the
consumer define the protocol it accepts.  Rust traits are nominal and explicit:
an implementation is declared for a type, and traits may also contain defaults
and associated items.  `s7contract` maps these ideas to S7 as follows.

```text
S7 generic       operation, e.g. area(x)
S7 method        implementation for a class
Go-like interface set of required S7 generics
Rust-like trait  explicit implementation record plus S7 methods
```

This also clarifies what kind of "type" an interface defines.  An S7 class is a
nominal representation type: it says how an object is constructed and validated.
An interface is a behavioral or protocol type: it says what operations must be
available.  Both are useful, but they answer different questions.

Packages such as `lambda.r` explore a different functional-programming route in
R, with pattern-matching-style function clauses, guards, and optional type
constraints.  `s7contract` stays closer to S7: it does not create a new function
clause language.  By default it checks whether S7 can find methods for required
generics, or whether an explicit trait implementation has been registered.
Optional argument and return specifications can be checked when evaluating a
call with `with()` or `%::%`.

## A structural interface

The classic drawing example remains useful because the behavior is visible.  A
`Drawable` object is anything for which S7 can find a `draw()` method.  In real
packages, this interface would usually live near the consumer that needs to draw
things, not necessarily in the package that defines `Circle`.

```{r structural-interface}
area <- new_generic("area", "x")
draw <- new_generic("draw", "x")

Circle <- new_class("Circle", properties = list(r = class_double))
Rect <- new_class("Rect", properties = list(w = class_double, h = class_double))

method(area, Circle) <- function(x) pi * x@r^2
method(draw, Circle) <- function(x) sprintf("circle(r = %s)", x@r)
method(area, Rect) <- function(x) x@w * x@h

Drawable <- new_interface("Drawable", generics = list(draw = draw))
Shape <- new_interface("Shape", generics = list(area = area), parents = Drawable)

implements(Circle, Shape)
implements(Rect, Shape)
missing_requirements(Rect, Shape)
```

A consumer keeps ordinary S7 style.  The assertion documents the expected
behavior; the actual call is still normal dispatch through `draw(x)`.

```{r structural-consumer}
render <- function(x) {
  assert_implements(x, Drawable)
  draw(x)
}

render(Circle(r = 2))
```

The same boundary is convenient in tests.  A mock only needs the behavior the
consumer asks for.

```{r structural-mock}
MockDrawable <- new_class("MockDrawable")
method(draw, MockDrawable) <- function(x) "mock drawing"

render(MockDrawable())
```

This is the main reason structural interfaces fit S7 well.  They add a small
runtime check around a dispatch model that S7 already has.  The practical API
shape is the Go maxim adapted to R: accept objects that satisfy a small protocol;
return ordinary, concrete R or S7 values.

## When the whole protocol is a class family

Some R APIs intentionally define a large protocol up front.  DBI is the useful
example: it is not just one consumer-local interface, but a package-level
standard built around nominal connection, driver, and result classes plus many
generic functions.  That fits the "abstract data type" exception to the
point-of-use rule.

In S7, the analogous design is a class family for identity and representation,
plus generics for behavior.  Consumers can still depend on a smaller interface
when they only need part of the protocol.

```{r dbi-like-class-family}
DatabaseConnection <- new_class("DatabaseConnection", abstract = TRUE)
MemoryConnection <- new_class(
  "MemoryConnection",
  parent = DatabaseConnection,
  properties = list(tables = class_list)
)

db_tables <- new_generic("db_tables", "con")
db_read_table <- new_generic(
  "db_read_table",
  "con",
  function(con, name) S7_dispatch()
)

method(db_tables, MemoryConnection) <- function(con) names(con@tables)
method(db_read_table, MemoryConnection) <- function(con, name) con@tables[[name]]

TableReader <- new_interface(
  "TableReader",
  generics = list(
    db_tables = interface_requirement(db_tables, returns = class_character),
    db_read_table = interface_requirement(
      db_read_table,
      args = list(name = class_character),
      returns = class_data.frame
    )
  )
)

first_table <- function(con) {
  assert_implements(con, TableReader)
  db_read_table(con, db_tables(con)[[1]])
}

con <- MemoryConnection(tables = list(iris = head(iris, 2)))
first_table(con)
```

The class says "this object is a database connection".  The interface says
"this consumer needs table-reading behavior".  A full DBI-like package may own
the broad class family; ordinary downstream functions should still prefer the
smallest protocol they use.

## Progressive argument and return checks

Interface requirements can optionally carry argument and return specifications.
The default is permissive: unspecified arguments are not checked, and the return
specification defaults to `S7::class_any`.  When specifications are present,
expressions can be evaluated in a contract mask with either `with()` or the
lambda.r-style `%::%` operator.  Calls to required generics inside that
expression are checked.

```{r progressive-interface}
Canvas <- new_class("Canvas")

draw_on <- new_generic(
  "draw_on",
  c("x", "canvas"),
  function(x, canvas, position, ...) S7_dispatch()
)

method(draw_on, list(Circle, Canvas)) <- function(x, canvas, position, ...) {
  sprintf("circle(r = %s) at %s", x@r, position)
}

DrawableOnCanvas <- new_interface(
  "DrawableOnCanvas",
  generics = list(
    draw_on = interface_requirement(
      draw_on,
      args = list(canvas = Canvas, position = class_integer),
      returns = class_character
    )
  )
)

canvas <- Canvas()
circle <- Circle(r = 2)

implements(Circle, DrawableOnCanvas)
with(DrawableOnCanvas, draw_on(circle, canvas, position = 1L))
draw_on(circle, canvas, position = 1L) %::% DrawableOnCanvas

checked_draw <- with(DrawableOnCanvas, {
  function(x) draw_on(x, canvas, position = 1L)
})
checked_draw(circle)
```

A method can satisfy the S7 method shape but still return the wrong kind of
value.  The checked call, including a function returned from `with()`, catches
that after ordinary S7 dispatch has run.

```{r progressive-return-failure}
BadCircle <- new_class("BadCircle", properties = list(r = class_double))
method(draw_on, list(BadCircle, Canvas)) <- function(x, canvas, position, ...) {
  x@r
}

tryCatch(
  with(DrawableOnCanvas, draw_on(BadCircle(r = 2), canvas, position = 1L)),
  error = function(e) conditionMessage(e)
)

tryCatch(
  checked_draw(BadCircle(r = 2)),
  error = function(e) conditionMessage(e)
)
```

The input checks use S7 classes and S7 multiple dispatch.  In this example,
`canvas` is also a dispatch argument, so `implements(Circle, DrawableOnCanvas)`
asks S7 for a `draw_on(<Circle>, <Canvas>)` method.  The return value can only be
checked after the call has run, which is why `with()` and `%::%` are useful.

## Number-like behavior

A general `Number` interface is tempting, but it should be treated carefully.
Base R arithmetic includes vectorization, recycling, missing values, attributes,
and binary operations.  A small number-like protocol is more honest: it says
only which operations a particular consumer needs.

```{r number-interface}
num_zero <- new_generic("num_zero", "x")
num_add <- new_generic("num_add", "x")
num_scale <- new_generic("num_scale", "x")

NumberLike <- new_interface(
  "NumberLike",
  generics = list(
    zero = num_zero,
    add = num_add,
    scale = num_scale
  )
)

method(num_zero, class_double) <- function(x) 0
method(num_add, class_double) <- function(x, y) x + y
method(num_scale, class_double) <- function(x, k) x * k

implements(class_double, NumberLike)
num_add(10, 5)
num_scale(10, 0.5)
```

This interface does not prove mathematical laws such as associativity or an
identity element.  It only says that these operations are present.  If those laws
matter, they should be described in the documentation and tested with examples
that are specific to the domain.

## Vector-like behavior

A vector-like contract is often more practical.  Many algorithms only need a
length, a way to slice, and a way to expose values.

```{r vector-interface}
vec_length <- new_generic("vec_length", "x")
vec_slice <- new_generic("vec_slice", "x")
vec_values <- new_generic("vec_values", "x")

VectorLike <- new_interface(
  "VectorLike",
  generics = list(
    length = vec_length,
    slice = vec_slice,
    values = vec_values
  )
)

ReadDepth <- new_class(
  "ReadDepth",
  properties = list(
    position = class_integer,
    depth = class_double
  ),
  validator = function(self) {
    if (length(self@position) != length(self@depth)) {
      "@position and @depth must have the same length"
    }
  }
)

method(vec_length, ReadDepth) <- function(x) length(x@depth)
method(vec_slice, ReadDepth) <- function(x, i) {
  ReadDepth(position = x@position[i], depth = x@depth[i])
}
method(vec_values, ReadDepth) <- function(x) x@depth

coverage <- ReadDepth(
  position = 1:5,
  depth = c(12, 15, 9, 20, 17)
)

implements(coverage, VectorLike)
vec_values(vec_slice(coverage, 2:4))
```

A function can depend on this small protocol without knowing how the object is
represented internally.

```{r vector-consumer}
window_mean <- function(x, i) {
  assert_implements(x, VectorLike)
  mean(vec_values(vec_slice(x, i)))
}

window_mean(coverage, 2:4)
```

This kind of interface is best used at package boundaries.  It is not meant to
replace base vectors, S7 classes, or mature vector frameworks; it names the small
piece of behavior a consumer needs.

## An explicit trait

A Rust-like trait adds nominal intent.  A class may have the right methods
structurally, but it does not have the trait until `impl_trait()` records that
implementation.

```{r trait}
perimeter <- new_generic("perimeter", "x")

Measurable <- new_trait(
  "Measurable",
  methods = list(
    area = trait_method(area),
    perimeter = trait_method(perimeter, default = function(x) NA_real_)
  ),
  assoc_consts = c("UNITS")
)

impl_trait(
  Measurable,
  Circle,
  methods = list(area = function(x) pi * x@r^2),
  assoc_consts = list(UNITS = "unitless"),
  replace = TRUE
)

has_trait(Circle, Measurable)
trait_call(Measurable, "area", Circle(r = 2))
trait_call(Measurable, "perimeter", Circle(r = 2))
trait_assoc_const(Measurable, Circle, "UNITS")
```

The useful distinction is intent.  A structural interface asks whether operations
are available.  An explicit trait asks whether a package author has declared a
class to implement a named contract.  The trait layer can also store associated
metadata such as `UNITS`, which is awkward in a purely structural interface.

## A Haskell-style dictionary object

Haskell type classes are often explained as dictionaries: a `Monad m`
constraint is operationally evidence that `m` has `pure` and `bind`.  R can
model that idea directly because functions are first-class values and S7 can
validate function-valued properties.

The example below defines a tiny `Maybe` algebraic data type, then stores its
monad operations in an S7 dictionary object.  The `s7contract` interface checks
that the dictionary exposes the operations a consumer expects.

```{r monad-dictionary}
Maybe <- new_class("Maybe", abstract = TRUE)
Nothing <- new_class("Nothing", parent = Maybe)
Just <- new_class("Just", parent = Maybe, properties = list(value = class_any))

MonadDict <- new_class(
  "MonadDict",
  properties = list(
    name = class_character,
    pure = class_function,
    bind = class_function
  )
)

dict_pure <- new_generic("dict_pure", "x")
dict_bind <- new_generic("dict_bind", "x")

MonadDictionary <- new_interface(
  "MonadDictionary",
  generics = list(
    pure = dict_pure,
    bind = dict_bind
  )
)

method(dict_pure, MonadDict) <- function(x, value) {
  (x@pure)(value)
}
method(dict_bind, MonadDict) <- function(x, mx, f) {
  (x@bind)(mx, f)
}

MaybeMonad <- MonadDict(
  name = "Maybe",
  pure = function(value) Just(value = value),
  bind = function(mx, f) {
    if (S7_inherits(mx, Nothing)) {
      Nothing()
    } else {
      f(mx@value)
    }
  }
)

implements(MaybeMonad, MonadDictionary)
```

Now the dictionary can be passed around as an ordinary R object.

```{r monad-dictionary-use}
dict_bind(
  MaybeMonad,
  Just(value = 2),
  function(x) dict_pure(MaybeMonad, x + 1)
)

dict_bind(
  MaybeMonad,
  Nothing(),
  function(x) dict_pure(MaybeMonad, x + 1)
)
```

The interface checks operation availability.  The monad laws are semantic
properties, so they belong in tests.  A small law check can still be written in
plain R.

```{r monad-laws}
maybe_equal <- function(x, y) {
  if (S7_inherits(x, Nothing) && S7_inherits(y, Nothing)) {
    return(TRUE)
  }
  if (S7_inherits(x, Just) && S7_inherits(y, Just)) {
    return(identical(x@value, y@value))
  }
  FALSE
}

f <- function(x) dict_pure(MaybeMonad, x + 1)
g <- function(x) dict_pure(MaybeMonad, x * 2)
mx <- Just(value = 10)

c(
  left_identity = maybe_equal(
    dict_bind(MaybeMonad, dict_pure(MaybeMonad, 10), f),
    f(10)
  ),
  right_identity = maybe_equal(
    dict_bind(MaybeMonad, mx, function(x) dict_pure(MaybeMonad, x)),
    mx
  ),
  associativity = maybe_equal(
    dict_bind(MaybeMonad, dict_bind(MaybeMonad, mx, f), g),
    dict_bind(MaybeMonad, mx, function(x) dict_bind(MaybeMonad, f(x), g))
  )
)
```

This is not Haskell's static kind system.  It is a concrete R/S7 encoding of the
same operational idea: a type-class instance can be represented as a runtime
object containing functions, and an interface can state which functions must be
available.

## Which feels more natural?

For functional OOP in S7, Go-like structural interfaces are the default fit.  S7
already makes generic functions the center of dispatch, so an interface as a set
of required generics is small and idiomatic.

Rust-like traits are heavier but useful when accidental compatibility would be a
problem.  They make sense for plugin systems, adapters, or domain protocols where
a package should explicitly claim conformance and provide metadata or defaults.

The practical rule is simple: start with a structural interface when the consumer
only needs behavior; use a trait when the declaration itself carries meaning.

## References

- The S7 package documentation: <https://rconsortium.github.io/S7/>.
- S7 issue #34, "Traits": <https://github.com/RConsortium/S7/issues/34>.
- The Go specification, especially interface types: <https://go.dev/ref/spec#Interface_types>.
- Chewxy, "How To Use Go Interfaces": <https://blog.chewxy.com/2018/03/18/golang-interfaces/>.
- The Rust book chapter on traits: <https://doc.rust-lang.org/book/ch10-02-traits.html>.
- The Rust reference chapter on traits: <https://doc.rust-lang.org/reference/items/traits.html>.
- The `lambda.r` package on CRAN: <https://cran.r-project.org/package=lambda.r>.
