8 ms·
Value-Oriented Programming
- wmccullough 8y agoSerious 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.
- brightball 8y agoIn my experience, a practice that requires training to implement rather than enforcement by the language itself will become messy. Every OOP server side project that I’ve dealt with in the last 16 years eventually becomes an entangled mess. That’s across Java, Python, Ruby, PHP and C#. Probably the biggest advantage Ruby has over the rest is the easy of monkey patching far up the inheritance tree where needed rather than having to work around everything below. All that said, functional/compositional approaches avoid these issues at the language level by default. They are a huge win for maintenance long term. It’s one of the big reason I got interested in Go and Elixir in the first place.
- wmccullough 8y agoThank you for the explanation! If someone such as myself wanted to understand the philosophy of functional languages, do you have any book you'd recommend? Learning the languages is easy enough, but if I ever tried them I'd want to do it using the ideals of functional programming vs. trying to contort the language to be something it isnt't. I'm noticing a trend more and more where developers move to FP over OOP after a decade or so in their career. I feel if I don't give it a look, I could be missing an obvious lesson.
- brightball 8y agoThere are different extremes. Elixir is one that keeps things developer friendly and enforces everything. The new Programming Elixir book is probably a good place to start. It has constructs that definitely smooth the transition...and it’s really just a wonderful language. Plus the runtime is excellent for server side.
- AlexanderNull 8y agoWhile Scala isn't a pure functional language there are some good resources for picking up FP related concepts with it (and it's what I recently moved over to so it's most recent for me). Scala with Cats did a pretty good job for me at describing things in relatable terms. "The Red Book" (Functional Programming in Scala) goes a bit more in depth on these concepts but has a pretty steep learning curve compared to the cats book. Also check out anything Haskell/Eta/OCaml for server side (and probably also a bit of a steep learning curve) or if you're currently a front end developer CHECK OUT ELM NOWWWWWW. Core beginner concepts to youtube would be currying, higher order functions, and composition. Monads will come once you've mastered map/flatMap/reduce/fold chaining.
- lmm 8y agoI would recommend ML for the Working Programmer (available free online on the author's site). Learning ML forced me to write in a properly functional style rather than writing "C in $LANGUAGE" (the book doesn't even mention the possibility of using mutable variables until quite late on); by the same token I'm not sure the language is that usable in the "real world" (I work in Scala these days), but the lessons from it were useful even when I was working in Python.
- sevensor 8y agoI think the first sentence is key, "reusing code through inheritance" is the fragile thing that doesn't work. Pure interfaces do better, but then you're not reusing much code by inheritance. Programming to interfaces lets you reuse the code that uses the interface, but not the code that implements it. That's my take, anyhow.
- throwawayjava 8y agoMore importantly, this forces you to create an explicit interface for any code you'd like to reuse between implementations. Inheritance is fragile for a lot of reasons (see e.g., fragile base class). But the most common failure modes all have the same thing in common: the error happens because a developer fails to realize that there's an implicit interface between two things in the object heirarchy.
- sevensor 8y ago> there's an implicit interface between two things in the object heirarchy I think the tendency to miss this is compounded by fuzzy thinking about class hierarchies. I've lost count of the times I've heard somebody talk about an object of a child class "talking to" an object of the parent class, when calling an inherited method. They think there's actually another object -- their mental model for inheritance is composition. Using pure interfaces helps to quash this kind of thinking.
- naasking 8y ago> Implementing an interface is not the same thing as inheritance. Correct, but typical OO languages implement interfaces via inheritance since that's the only means of extension. The problem with inheritance is that it conflates subtyping with subclassing. These are separable concerns, so with interfaces you get proper subtyping without subclassing, but you can't typically have subclassing without subtyping which is why inheritance should generally be avoided.
- nexys 8y agoWhat is the difference between subtyping and subclassing? It looks confusing to me.
- zwieback 8y agoOversimplifying for this context: subclassing is reusing the implementation of a parent class, subtyping is when a type can be substituted for a (parent) class for some given functionality. It's confusing because it's not always easy to explain what constitutes "substitute", e.g. see "Liskov Substitution Principle". In some languages there might not be any class or interface relationship between subclasses - they just have the right functions that do the right things.
- kroltan 8y agoAs I understand it, subtyping is the actual conflation of interfaces (not necessarily a syntatical `interface`, but in the logical sense of "exposed things") into one type, and subclassing is direct extension, the so-called "is a" constraint. For example, in Rust, there is no subclassing: There is no concrete type that is also another type. But there is subtyping, that is, a concrete type can be used where a interface (in Rust, called `trait`) is required, granted it implements that interface. Additionally, external sources can implement their interfaces unto existing external types. In C#, you have subtyping+subclassing, but only as one. That is, you can have a concrete type B that is an A, and if A implements X, then B implements X. But you can't implement Y for A unless you have access to A's source. So if you want a type that is an A but also does Y, you _have_ to subclass A as a B, and implement Y on B.
- hinkley 8y agoI was responsible for a shared library amongst a bunch of teams that didn’t like each other and one greedy global contractor. The latter in particular wanted more money any time the interface changed (even when there was evidence they hadn’t consumed it yet) What I was handed was slow as molasses and insecure by design (no surprise since I arrived at late POC stage). I spent a considerable fraction of my cleverness on that project to upend the semantics and internal logic of that module with only a handful of changes to the syntax. And most of that came down to massaging the contract and a couple method signatures. Outlawyering that team is still one of my proudest moments, but I’ve lost less sleep after I left over that code than just about anything I’ve done.
- dunham 8y agoA "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".
- Someone 8y agoEquivalent, but not exactly the same, mostly in the sense that Java is less dynamic than Swift. Ones I know of: - In Java, you have to declare the interfaces that a class implements when you declare the class. In Swift, you can add conformance of a class to a protocol, even if you don’t have access to the class’s source code. - In Swift, protocol definitions can contain implementations of methods (I think this is being or has already been added to Java recently). For example, a method foo that takes a string argument could call the foo overload taking a character for each character in the string. That way, classes conforming to the protocol need not define the method taking a string. - Even if the protocol definition doesn’t declare that default implementation, you can add one by writing an extension method. All of those are useful, but also can make it hard to understand what code exactly is being called. (More info at https://docs.swift.org/swift-book/LanguageGuide/Protocols.html https://docs.swift.org/swift-book/LanguageGuide/Protocols.ht...)
- saagarjha 8y ago> In Swift, protocol definitions can contain implementations of methods Technically, no, they can't. What you do is write your protocol as usual, then extend the protocol with a default implementation.
- TeMPOraL 8y ago> "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.
- deleted 8y ago[deleted]
- deleted 8y ago[deleted]
- lmm 8y agoThe line is about implementing interfaces rather than doing "extends"-style inheritance, which effectively conflates three different things: implementing the superclass' (possibly implicit) interface, composing in a value of the superclass and delegating method implementations to it.
- nine_k 8y agoThe 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.
- beders 8y agoWas about to comment in a similar fashion. One more example of a language who has embraced this: Clojure Protocols, polymorphism, dynamic dispatch (i.e. multi-methods) and immutability baked in.
- AstralStorm 8y agoObviously then keep the specification, preferably testable, formal and verifiable of the protocol. Without this part, you end up with a real big mess similar to 90s internet.
- hinkley 8y agoIt’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.
- skybrian 8y agoIt seems like the downside would be performance due to allocating a lot of transient objects? Maybe drawing isn't the best example here.
- blt 8y agoThis could be solved in a language like python using generator expressions, or in C++ with iterators.
- charlesism 8y agoIn 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 8y agoIt 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 8y agoI 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 8y agoThat 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.
- aeleos 8y agoTo 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.
- jmull 8y agoInteresting. 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.
- atombender 8y agoAs 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.
- hderms 8y agoAgreed except product types aren't the same as sum types, product types are records or what have you
- atombender 8y agoYou're right, I was thinking of something else.
- marcus_holmes 8y agoAs 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...
- 8y ago