9 ms·
Functor-Oriented Programming
- vittore 9y agoI really want to see examples of this.
- dbmikus 9y agoThe unification-fd library referenced in the article is probably a pretty good example. I have not looked at it, though, and it would be nice to have explicit bits of code pointed out in an explanation.
- danidiaz 9y agoI've seen mentioned the "streaming" package as an example of this: http://hackage.haskell.org/package/streaming http://hackage.haskell.org/package/streaming "streaming" provides a Stream type which is parameterized by a Functor. The main module in the package provides very general functions which work for any functor and often involve natural transformations and functor compositions and sums. http://hackage.haskell.org/package/streaming-0.1.4.5/docs/Streaming.html http://hackage.haskell.org/package/streaming-0.1.4.5/docs/St... The "Streaming.Prelude" instantiates the functor with a pair type and provides the more concrete API that one would expect from a streaming library. http://hackage.haskell.org/package/streaming-0.1.4.5/docs/Streaming-Prelude.html http://hackage.haskell.org/package/streaming-0.1.4.5/docs/St...
- roflc0ptic 9y agowriting DSLs with Free monads is an example of this. Recursion schemes are another example - see kmett’s library in Haskell, or Matryoshka in Scala.
- fmap 9y agoThere is an easy design choice - which Haskell didn't take - which would allow us to have transparent Identity functors, associative Compose and many others: Add a conversion rule to your language and don't eagerly expand definitions at the type level. For example, in Coq you would write Definition Identity a := a. Definition Compose F G a := F (G a). Instance: Functor Identity. Proof[...] and so on. This works, since explicit conversion during type checking allows you to associate type classes to otherwise transparent definitions. Of course, this is no silver bullet and complicates other things. Everything works out beautifully though, if you combine conversion with bidirectional type checking, at the expense of less powerful type inference.
- hyperpape 9y agoInterestingly, the author did a dissertation using Coq, so I wonder if he thinks that it's not really suitable.
- fmap 9y agoI don't know, but Russel O'Connor got his PhD in 2008, the same year that type classes were introduced in Coq. There has been a lot of development on the Coq proof assistant since then. I don't imagine that working with Coq in 2008 was particularly pleasant. :)
- auggierose 9y agoI don't think working with Coq has ever or will ever be particularly pleasant :-) O'Connor gave a nice talk at TPHol's which I attended: https://arxiv.org/abs/cs/0505034 https://arxiv.org/abs/cs/0505034 Keeping up with Coq shouldn't have been too difficult for him.
- lmm 9y agoWhat does Idris do? That seems to be the best hope of combining Coq-like advanced typing with Haskell-like practicality.
- runeks 9y agoIt would be nice to know what kind of code OP writes. Ie. are we talking application code or library code, and if it's libraries are we talking networking libraries, graphics libraries or math libraries? Usually I find that Haskellers write very "low-level" code, ie. implement math abstractions or the like, while I prefer to write applications (in Haskell), where this abstraction technique may not be suitable.
- hyperpape 9y agoLow level stuff, at least in public: http://r6.ca/code.html http://r6.ca/code.html
- Bromskloss 9y ago> application code or library code Do the two call for different styles?
- runeks 9y agoI’d argue that they do to some extent. With application code you’re interfacing with some real-life system, and need to adapt to all the corner cases of this, whereas with library code you might be implementing something entirely theoretical, where everything fits together perfectly in some abstraction because there are no corner cases to consider (it’s just a clean math concept). With a math concept, the elegant concept already exists beforehand, and you just implement it. With application code, there might be 27 different but-ifs, and-also-remembers that you need to consider, where developing an elegant, coherent concept of the entire app is very challenging, or even impossible. With some application code, I find I’m often the first one to make sense of it all, taking all the corner cases and creating a coherent model out of it, i.e. making it all fit into some well-defined abstraction. And then a new corner case appears and my model falls on the floor, and needs to be redesigned. The definition of exact real arithmetic doesn’t change over time. There’s no customer who calls you up and asks you to add a feature that doesn’t fit into your existing model/abstraction.
- kmicklas 9y agoThis style is plenty useful in "higher level" stuff. Consider Reflex: http://docs.reflex-frp.org/en/latest/ http://docs.reflex-frp.org/en/latest/
- harveywi 9y agoSeems to be similar to the direction taken by John De Goes (using algebras to describe your problem domain, building programs from those algebras via free monads/applicatives/etc., interpreting those programs using natural transformations). I think there is definitely something to this kind of thinking. Both authors argue that modern programming languages do not have the best ergonomics for this style of programming, albeit it is at least possible to do most of it in languages such as Haskell and Scala. 1. Data Structures Are Antithetical to Functional Programming [http://degoes.net/articles/kill-data http://degoes.net/articles/kill-data] 2. A Modern Architecture for FP [http://degoes.net/articles/modern-fp http://degoes.net/articles/modern-fp] 3. Modern Functional Programming: Part 2 [http://degoes.net/articles/modern-fp-part-2 http://degoes.net/articles/modern-fp-part-2]
- roflc0ptic 9y agoAlso, he discusses some of the limitations of this approach here - https://m.youtube.com/watch?v=A-lmrvsUi2Y https://m.youtube.com/watch?v=A-lmrvsUi2Y He also calls for another language, although his asks for that language seem different.
- shalabhc 9y ago> Data Structures Are Antithetical to Functional Programming Interestingly I've heard a similar pov from the 'pure OO' (Smalltalk style) world as well. Specifically avoiding 'data structures' and embracing late binding.
- jcelerier 9y agoAnd both styles lead to equally unreadable code. If you can't take an average new graduate, put it in front of your code and have him be productive in your codebase in a few days it's no good.
- dmit 9y agoAn average new graduate wouldn't be productive in any codebase in a few days. And even if they were, that's a terribly low bar to set. Software development education sucks and most people have to learn on the job. So why not teach them ways to write more reliable and maintainable code.
- dbranes 9y agoThis is great. It's essentially asking you to work in a 2-category[1], which, once you get used to it, is an amazingly intuitive way to make sense of constructions in ordinary 1-categories. [1]https://ncatlab.org/nlab/show/2-category https://ncatlab.org/nlab/show/2-category
- AlexCoventry 9y agoAnyone got some good examples of this? > With functor oriented programming, polymorphism is not necessarily about using functions polymorphically. Instead, polymorphism provides correctness guarantees by ensuring that a particular function can only touch the specific layers of functors in its type signature and is independent of the rest of the data structure. One benefits from polymorphism even when a function is only ever invoked at a single type.
- danidiaz 9y agoI'm not 100% sure of what the author had in mind, but here's a possible (and possibly dumb) example. Imagine you need a 2-parameter "Lasagna" datatype wich is a non-empty list of alternating "a" and "b" elements. It could be used to model something like a timeline, perhaps. Instead of constructing it by hand, you could express it as a composition of other stuff: import Data.List.NonEmpty import Data.Bifunctor import Data.Bifunctor.Tannen type Lasagna = Tannen NonEmpty (,) example :: Lasagna Char Int example = Tannen $ ('a',3) :| [('b',4),('d',6)] http://hackage.haskell.org/package/base-4.10.0.0/docs/Data-List-NonEmpty.html http://hackage.haskell.org/package/base-4.10.0.0/docs/Data-L... https://hackage.haskell.org/package/bifunctors-5.4.2/docs/Data-Bifunctor-Tannen.html https://hackage.haskell.org/package/bifunctors-5.4.2/docs/Da... That type has a whole bunch of useful instances without even having to auto-derive them. And you could have functions that manipulated the pairs without caring what shape the "outer functor" has.
- lmm 9y agoImagine you have two functions that you only call with lists of integers: def summarize1[A](list: List[A]) = ... def summarize2(list: List[Int]) = ... Even though you don't call summarize1 polymorphically, the signature tells you that it's only looking at the structure of the list rather than what's inside it - a reader can immediately see that it's going to do something like return the length of the list. Whereas summarize2 could do other things, e.g. sum the elements of the list, add and subtract alternating elements... A list is a pretty simple structure, these kinds of guarantees become more useful when the structure itself is complicated. E.g. in the project I'm currently working on I have a tree-like datatype that only ever contains Strings, but I've written it generically so I can be confident that my structural functions only work on the structure, and I have a clear separation between functions that operate on the structure of the tree and functions that operate on the content of the tree.
- highmastdon 9y agoIt's not the same thing, but it triggered something. A programming language related is Koka (see: An overview of Koka: https://koka-lang.github.io/koka/doc/kokaspec.html#sec-an-overview-of-koka https://koka-lang.github.io/koka/doc/kokaspec.html#sec-an-ov...). It's basically a functional programming language where functions can be applied to a type. The type is then passed as the first parameter. So instead of `afunction(param1, param2)` you would do `param1.afunction(param2)`. Example: (https://koka-lang.github.io/koka/doc/kokaspec.html#sec-structs https://koka-lang.github.io/koka/doc/kokaspec.html#sec-struc...) struct person(age: int, name: string) fun birthday(p: Person) { return p(age = p.age+1) } val me = Person(27, "Albert") // { age: 27, name: "Albert" } val meWithBirthday = me.birthday() // { age: 28, name: "Albert" }
- gnaritas 9y agoThat's just OO without encapsulation.
- wtetzner 9y agoNo, it's just some sugar over plain old functions.
- gnaritas 9y agoSure, and functions are just sugar over goto's.
- wtetzner 9y agoThere's typically more to OO than just method syntax. Dynamic dispatch, subtyping, etc. And saying something is OO without encapsulation isn't really saying anything, given that encapsulation is arguably the most important part of OO.
- gnaritas 9y agoI'd argue that, it's not remotely the most important part nor is sub-typing. Encapsulation encourages better OO, but you can take a well encapsulated OO program, and remove all encapsulation by making all the data public, and that program is still a well structured OO program. OO is about the way objects talk to each other to accomplish work, the message, the API between objects; it's not about the many little things like encapsulation that encourage better messaging. There are dozens of practices that encourage better OO, but you can write good OO without them if you know what good OO is and don't need those training wheels.
- platz 9y agoGabriel Gonzalez: The functor design pattern http://www.haskellforall.com/2012/09/the-functor-design-pattern.html http://www.haskellforall.com/2012/09/the-functor-design-patt... is also a well-known post.
- vog 9y agoPardon my ignorance, but can't this be more easily translated into terms of (modern) object oriented programming, where you avoid mutating state? Here, the "natural transformation" is a function that takes an input object which adheres to a specific interface (i.e. provides a set of specific methods), operates only on that input interface, and returns another object that conforms to the expected output interface. Avoiding mutating state means that either those objects have no attributes, or that the state can only be set on construction. (In FP speak, this corresponds currying of common parameters. Or to an internal abstract type, if the constructor is private and you can only create it via a specific set of static methods). Is this modern style of OOP equivalent to "Functor-Oriented Programming", or am I missing something?
- deleted 9y ago[deleted]
- kraghen 9y agoOne important aspect of programming with functors is the ability to quantify over them, i.e. higher-kinded types. This is crucial for building reusable components on the functor-level of abstraction. In modern OOP this would amount to quantifying over interfaces which is typically not even possible in those languages.
- lmm 9y ago> Avoiding mutating state means that either those objects have no attributes, or that the state can only be set on construction. (In FP speak, this corresponds currying of common parameters. Or to an internal abstract type, if the constructor is private and you can only create it via a specific set of static methods). I don't think the style you're talking about is something that most people would call "object oriented programming" ("modern" or otherwise). Indeed the usual name for the programming style in which you avoid mutating state is "functional programming".
- vog 9y agoMaybe, but the terms "class", "method" and "interface" are more commonly understood than "functor" or "natural transformation". So for better understanding by the general public it may make sense to offer an explaination in terms of OOP, even if what is described is more FP style than OO style.
- dmitriid 9y ago> Instead of writing transformations between data structures, one writes natural transformations between functors, where a natural transformation between functors F and G is a polymorphic function of type forall a. F a -> G a. There's nothing natural about this. ;)
- z3t4 9y agoA strict type system makes sure your program is statically correct, and catches many bugs caused by the programmer. The real world though is dynamic and I prefer to check all input data at runtime, eg. each function check if what's passed into it is correct. It not only catches a lot of bugs, those caused by the programmer and the ones caused by interacting with the outside word, it also makes the code easier to test. This probably sounds like I'm swearing in church to a Haskel programmer though.
- thinkpad20 9y agoI'm not sure what you mean, or perhaps you have an incorrect understanding of how Haskell works. Of course data is still checked at runtime; for example a parser will check that a string is formatted in valid way, or a lookup in a hash table will check that a key is valid. This is done at runtime as it would be in virtually any language. However there's no way in Haskell (short of low-level type system tricks) to check that a function with an argument of type `Int` actually receives an `Int` when it is called. There's no need to, because it is guaranteed by the type system. The strength of these guarantees, coupled with the ability to define rich, expressive types, are what makes Haskell programs very robust. For example you can represent all possible results of a valid JSON parse: data Value = Object (HashMap Text Value) | Array (Vector Value) | String Text | Number Float | Bool Bool | Null Which variant of this type is actually encountered will not be determined until runtime, so the program is required to respond to the different cases; however a function which takes type `Value` will never need to worry that it doesn't actually receive one of the above variants at runtime.
- Fire-Dragon-DoL 9y agoAny chance someone can provide code examples, maybe in a bunch of different languages (elm, haskell and even js functional)? Would love to understand this more