
Value-Oriented Programming - glhaynes
https://matt.diephouse.com/2018/08/value-oriented-programming/
======
nine_k
The idea proposed: use protocols and value types. This avoids the problems
with subclasses and with mutability (concurrent modification). Enums are also
used as a controllable way to have different kinds of related objects and use
a switch statement to distinguish them.

It is fun to see how the benefits of immutability, typeclasses, and (sort of)
ADTs / pattern matching are being discovered independently.

~~~
hinkley
It’s a compromise between structural and nominal typing to boost code reuse
without locking yourself prematurely into structural typing. It’s been tried
before and has been given different names.

Personally I’m fond of it. It’s one of several ways to blur the line between
“is a” and “has a” relationships. We used to go out of our way to distinguish
the two (eg in UML) but it has fallen out of favor.

A coworker long ago convinced me that improvements to delegation would be
coming soon to new languages and unfortunately this is one of the first times
I’ve seen it happen close to the way he hoped.

~~~
sitkack
Delegation is extremely powerful, but it is easily implementable in most OO
and Functional languages, no?

~~~
lmm
If you have to implement it by hand you end up not doing it, especially in
languages where "extends" is right there.

------
jmull
Interesting.

I know this is just an example, but it's distracting that a drawable is
expressed as a set of paths. Order matters in drawing! (Generally, anyway)
Actually, it's a bit bad that the collection is explicitly instantiated at
all. It would be better if "paths" was only a sequence of Paths.

------
charlesism
In theory: fine. In practice: it's Swift. I would think very carefully before
using a bunch of enums with payloads (aka " associated values"). The current
syntax means you will probably wind up with either unreadable code, or _a lot_
of extra complexity. I hope in the future, Swift improves the syntax for
comparing, creating payload enums, and accessing their payloads. Until then,
there are pros, but large cons too.

~~~
__sdegutis
It sounds closer to Haskell to me. ADTs are very similar to associated values
that Swift has, although I think they're on a slightly higher type level. I
haven't found Haskell too unreadable but that's probably because it ditched
C-style syntax whereas Swift stuck with it. Maybe it's a trade-off worth
making though, since sticking with familiar syntax lowers the bar.

~~~
charlesism
I don't know how Haskell does it, but, at present, Swift enums _with payloads_
have caveats that simple Swift enums don't:

\- they use this verbose syntax to access payloads "if case let .foo(bar) =
baz { /* do something with bar */ }" The alternative being to do everything in
a switch statement, and possibly create a local variable thanks to scope

\- their payloads are not convertible to tuples, despite their cosmetic
similarity

\- they do not allow default values, despite their cosmetic similarity to
swift function parameters (which do support default values)

\- they aren't automatically Hashable and Equatable

\- they aren't automatically included in the Enum's array of ".allCases"

The great thing about Swift is that all of these issues can be worked around
using extensions. Unfortunately (the way I do things, anyways) that means a
lot of the time in Swift, the Enum itself is maybe 5 lines long, while my
extensions to work around the above limitations go on and on for another 30+
lines.

~~~
__sdegutis
That sounds like a failure of a language to me. When you have to create long
ugly hacks to work around the language's limitations, then it's not working
for you but against you. I've been writing in Objective-C ever since
abandoning Swift 2 and so far I've only really ever heard people say they're
having this or that major difficulty with Swift, so it makes me feel like it
was the right thing to do to avoid getting heavily invested in Swift.

------
atombender
As someone who mostly develops in Go these days, I have envy of languages like
Rust, Swift, OCaml and Haskell where "value-oriented programming" actually
have rich language support.

In particular, "value types" are really useful for ASTs, configurations and
other DSLs where you have graphs of polymorphic values.

For this, you really need sum types (often called tagged unions or enums), as
illustrated in the article. In Go, the absence of a sum type mechanism means
you have to use interfaces as a kludge. In a current project we have something
like this:

    
    
      type Expr interface {
        isExpr()
      }
    
      type Identifier struct {
        Name string
      }
      func (Identifier) isExpr()
    
      type FuncCall struct {
        Name string
        Args []Expr
      }
      func (FuncCall) isExpr()
    
      // ...
    

instead of something like:

    
    
      type Expr enum (
        Identifier struct {
          Name string
        }
        FuncCall struct {
          Name string
          Args []*Expr
        }
      }
    

One annoyance in Go is that the compiler can't prove that your type switches
are exhaustive, but there are other downsides, such as that all values must be
boxed inside an interface.

~~~
marcus_holmes
As a Go programmer myself, I read this whole article thinking "well done,
you've implemented Go's structs + interfaces model into Swift!"

I'm curious about the enum thing, though... I think the closest we get at the
moment is the sql Null<Type> structs, where the value can either be null or a
valid value, and we have to jump through hoops to work out which? Is that what
you're talking about?

How would you see this working in Go? It is coming up to Go v2 after all,
totally the right time to propose stuff like this...

~~~
atombender
The first part of the article (which summarizes a WWDC talk on protocols)
presents something you can do with interfaces and structs in Go. But in the
second part, where the author presents a superior pattern based on values and
sum types, is precisely something you can't currently do in Go.

To do what he proposes, you need sum types akin to what Rust calls enums. In
Rust it would be something like:

    
    
      enum Path {
        Arc {
          center: CGPoint;
          radius: CGPoint;
          start_angle: CGFloat;
          end_angle: CGFloat;
        },
        Line {
          start: CGPoint;
          end: CGPoint;
        }
      }
    

The idea is that you have a value declared as Path, you can _only_ assign Arc
and Line to it. Similarly, if you want use a value, you have to match against
it:

    
    
      match value {
        Path::Arc => ...,
        Path::Line => ...,
      }
    

or you can destructure:

    
    
      match value {
        ...,
        Path::Line { start, end } => {
          draw_line(start, end);
        },
      }
    

Languages that have sum types are usually expression-based, so the compiler
insists that your match is exhaustive, or it won't compile:

    
    
      // Fails with compilation error
      match value {
        Path::Arc => ...,
      }
    
      // This works
      match value {
        Path::Arc => { println!("is an arc"); },
        _ => { println("is something else"); },
      }
    

The closest Go has, as I said in my comment, is the ability to unify multiple
structs with a dummy interface, e.g.:

    
    
      type Path interface {
        isPath()
      }
    
      type Arc struct {
        center, radius CGPoint
        startAngle, endAngle CGFloat
      }
      func (Arc) isPath()
    
      // etc.
    

Now you can do a type switch:

    
    
      switch t := path.(type) {
        case Arc:
          ...
        case Line:
          ...
      }
    

However, since Go's interfaces are implicit, it doesn't know that Arc and Line
fulfill a possible union of types, and so the switch cannot fail if it's not
exhaustive:

    
    
      // This compiles
      switch t := path.(type) {
        case Arc:
          ...
      }
    

I'd love to have sum types in Go; it feels like a fairly natural fit for the
language since we already have high-level constructs like interfaces:

    
    
      type Path enum (
        Arc {     
          center, radius CGPoint
          startAngle, endAngle CGFloat
        }
        Line {
          start, end CGPoint
        }
      )
    

The syntax for matching could be exactly the same as today, even. It would be
to cool to be able match with destructuring, but given that Go doesn't support
any destructuring today, I'm OK with this being left out to keep the language
small.

As an aside, it's important to note that in Rust, enums aren't just structs;
they can be tuples, too, or constant values:

    
    
      enum State {
        Alive,
        Dead,
      }
    

Here, a value of type State can be either Alive or Dead:

    
    
      let s1: State = Alive;
      let s2: State = Dead;
    

And you can pattern match against it. The closest Go has to this is numeric
constants with help from the iota keyword:

    
    
      type State int
      const (
        Alive State = iota
        Dead
      )
    

But yet again, this is a hack that doesn't provide much type-system safety:

    
    
      var s State = 123  // Invalid, but compiles

~~~
marcus_holmes
thanks :) I get this now. This would be useful if Go supported it.

I've used enums before (but just as constant values), and I'd love to have
them in Go. Iota is nice, but as you say there's no constraint on the type. It
seems like a good fit...

------
wmccullough
Serious question about this line:

"Reusing code via inheritance is fragile. Inheritance also couples interfaces
to implementations, which makes reuse more difficult. This is its own topic,
but even OO programmers will tell you to prefer “composition over
inheritance”."

Isn't the goal to have implementations of interfaces so that you can inject
implementations around in order to reduce tight coupling? Implementing an
interface is not the same thing as inheritance. It's adhering an
implementation to a contract. Are interfaces in ObjC different than what I'm
used to from Java, C#, and others?

I mean this with sincerity, what am I missing? Is this a short-coming of my
knowledge because I come from a strict OOP background? I've always used
implementations of multiple interfaces to achieve composition.

~~~
dunham
A "protocol" in swift is equivalent to an "interface" in Java.

"protocol" seems to be the older name (per one of the guys that wrote Java):

> I'm pretty sure that Java's'interface' is a direct rip-off of Obj-C's
> 'protocol'
    
    
       -- https://cs.gmu.edu/~sean/stuff/java-objc.html
    

So "protocol oriented programming" is just advocating the use of swift's
equivalent of java interfaces. (It'd be "interface oriented programming" in
Java.)

Edit: apologies if you know all this already, it just sounded like you were
asking "what makes this protocol stuff better than interfaces".

~~~
TeMPOraL
> _" protocol" seems to be the older name (per one of the guys that wrote
> Java)_

Quite likelym as another Guy (Steele) responsible for Java was also a big name
in the Lisp world, including chairing the committee that wrote ANSI standard
for Common Lisp. In the Lisp world, protocols were a known concept. In CL
nomenclature, a protocol is a set of generic functions and types used by those
functions. Which is kind of like a Java interfaces, except with multiple
dispach based on any of the arguments instead of the typical single dispatch
of Java and friends.

For example from TFA, a CL protocol would be a #'draw generic function and a
'drawable and 'renderer abstract types. The particular #'draw methods could be
specialized on either argument (or both), and we could also imagine another
protocol consisting of (the same) renderer type and some geometry-related
generic functions.

Point being, protocols are both an old and very useful concept, and like
usual, C++/Java-like languages only allow expressing a small subset of it.

~~~
saurik
The term from Objective-C came directly from Smalltalk, which had almost
directly the same concept. Really, Objective-C is essentially "what if the
bodies of Smalltalk message implementations were coded in a low-level
language?".

I am fascinated, though, by your claim that Lisp also used this term... before
reading it I would have been 100% sure that it didn't. I just spent ten
minutes trying to find documentation of this, and can't find it... but I am
not a native speaker of Lisp and so don't always know where to look; can you
point me at a reference? (FWIW, I know of the meta-object protocol, which
afaik is a singular thing at a different level of abstraction, and I know
about classes and methods and generic functions and mulmethods, which seem
like what you are talking about but aren't described by protocol?)

~~~
TeMPOraL
Common Lisp doesn't use this term at the language level - nor does any other
Lisp I know, except Clojure. But it shows up around the language. Like, the
Metaobject Protocol. Or CLIM - the Common Lisp Interface Manager - has a whole
large spec defined mostly in terms of protocols[0].

Where do protocols originate from, I don't know. Maybe Smalltalk. But I first
met them in Common Lisp ecosystem.

\--

[0] - [http://bauhh.dyndns.org:8000/clim-
spec/2-5.html#_23](http://bauhh.dyndns.org:8000/clim-spec/2-5.html#_23)

~~~
lispm
Protocols were used in the original CLIM implementation:

[https://github.com/franzinc/clim2/blob/master/utils/protocol...](https://github.com/franzinc/clim2/blob/master/utils/protocols.lisp)

[https://github.com/franzinc/clim2/blob/master/clim/stream-
de...](https://github.com/franzinc/clim2/blob/master/clim/stream-
defprotocols.lisp)

~~~
TeMPOraL
Oh yes, I know. I was part of the effort of getting that codebase running on
modern CL implementations.

[https://github.com/dkochmanski/clim-
tos/graphs/contributors](https://github.com/dkochmanski/clim-
tos/graphs/contributors)

Two lessons I learned:

One, Common Lisp code is surprisingly stable over time, with 90% of it still
working on CCL/SBCL even though the codebase predates the language standard
and is older than I am (don't be mislead by that 1991 on Github, it's much
older).

Two, oh my god, trying to understand a large codebase seriously abusing
:before/:after/:around methods in large class graphs is _not_ an easy task,
though arguably it's more of an issue with available tooling. With better ways
to explore runtime program state, it would be much easier to understand and
improve such code. I may have written about this last night here:
[https://mastodon.technology/@temporal/100646861775747986](https://mastodon.technology/@temporal/100646861775747986).

Also fun fact, this project is the only case where macroexpanding code crashed
my SBCL...

Anyway, let that codebase be and serve as a historical reminder; for more
modern implementation of CLIM standard, I'll direct everyone to
[https://common-lisp.net/project/mcclim/](https://common-
lisp.net/project/mcclim/).

------
skybrian
It seems like the downside would be performance due to allocating a lot of
transient objects? Maybe drawing isn't the best example here.

~~~
blt
This could be solved in a language like python using generator expressions, or
in C++ with iterators.

------
aeleos
To me at first glance this seems very similar to the rust trait system, where
you start with a simple non object structure that describes only the functions
that an object requires.

