News for hackers. Stuff that matters.
Re:Value-Oriented Programming by atombender on Friday August 31, 2018 @06:35PM (#17889015)
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.
Re:Value-Oriented Programming by marcus_holmes on Friday August 31, 2018 @10:14PM (#17889758)
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...

Re:Value-Oriented Programming by atombender on Saturday September 01, 2018 @12:04AM (#17890141)
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
Re:Value-Oriented Programming by marcus_holmes on Saturday September 01, 2018 @01:40PM (#17892972)
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...

Re:Value-Oriented Programming by hderms on Friday August 31, 2018 @06:44PM (#17889057)
Agreed except product types aren't the same as sum types, product types are records or what have you
Re:Value-Oriented Programming by atombender on Friday August 31, 2018 @06:53PM (#17889094)
You're right, I was thinking of something else.