11 ms·
Functional Programming Self-Affirmations
- falcor84 2y agoI'm very disappointed. I was really hoping for something like the SRE affirmations - https://youtu.be/ia8Q51ouA_s https://youtu.be/ia8Q51ouA_s
- lupire 2y agoThis is fine but it's just a rehash of old well-knowned stuff. I don't see the value of learning this stuff one random blog post at a time. There are many books and established blogs with long series of material to give you an education.
- sensen 2y agoDo you have an example of the many books and established blogs that you can share? A newcomer might not be familiar with where to start when attempting to learn more about functional programming.
- cosmic_quanta 2y agoI'm a big fan of Haskell Programming from First Principles. That's where more advanced ideas like Monads started clicking. https://haskellbook.com/ https://haskellbook.com/
- mrkeen 2y agoLet's keep repeating it until it gets more adoption.
- MeetingsBrowser 2y agoKnown by you, maybe. None of these "affirmations" are common in any code base I work with, unfortunately.
- andrewflnr 2y agoIt's a pretty good collection and summary of ideas I've only seen scattered around, before.
- pyrale 2y ago> This is fine but it's just a rehash of old well-knowned stuff. A significant share of the current dev community wasn't there when these principles were first described. Sometimes, bringing up old topics once again helps seeing that many people had never heard of them yet.
- travisjungroth 2y agoCould you list the top 5 ideas, with a short summary and a link to a well-regarded blog post that goes into more detail for each?
- tubthumper8 2y ago~~I can't tell if you're being sarcastic, but that's exactly what TFA does. But just to pull it out explicitly:~~ Edit: I think I misunderstood your point. You were asking for a similar kind of list as TFA from the other commenter which may not necessarily be the *same* 5 ideas Leaving the rest here in case it helps someone: 1. Parse, don’t validate https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va... 2. Make illegal states unrepresentable https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/ https://fsharpforfunandprofit.com/posts/designing-with-types... 3. Errors as values https://jessewarden.com/2021/04/errors-as-values.html https://jessewarden.com/2021/04/errors-as-values.html 4. Functional core, imperative shell https://www.javiercasas.com/articles/functional-programming-patterns-functional-core-imperative-shell https://www.javiercasas.com/articles/functional-programming-... 5. Smart constructor https://wiki.haskell.org/index.php?title=Smart_constructors https://wiki.haskell.org/index.php?title=Smart_constructors
- travisjungroth 2y agoYep, was kinda being sarcastic. IMO these sorts of posts are really valuable. They don't seem valuable when you're already familiar with the ideas and have read all the posts. But for people who are knew, they can really accelerate things. The comment kinda reminded me of the forum comments that will answer questions with "just use Google" and another person replies "I found this thread with Google ;(".
- revskill 2y agoA class is just a function in the category of Javascript.
- lincpa 2y ago[dead]
- agentultra 2y agoThese are great ideas and patterns even if you’re not doing functional programming. FP-first/only languages tend to push you in these directions because it makes programming with them easier. In languages where FP is optional, it takes discipline and sometimes charisma to follow these affirmations/patterns/principles.. but they’re worth it IMO.
- greener_grass 2y agoI'm not convinced that you can follow all of these outside of Functional Programming. How can you "Make illegal states unrepresentable" with mutable state and sequences of mutations that cannot be enforced with the type system? How can you do "Errors as values" at a large scale without do-notation / monads? How can you do "Functional core, imperative shell" without the ability to create mini DSLs and interpreters in your language?
- tubthumper8 2y agoGenuinely curious for all these follow up questions: Is immutability exclusive to functional programming? Is the ability to use data/values exclusive to functional programming? Are monads exclusive to functional programming? For discussions like this, how do we separate "it was done first in functional programming but can also be done in procedural programming" with "it cannot be followed outside of functional programming"?
- greener_grass 2y ago> Is immutability exclusive to functional programming? No, but immutable defaults are powerful. E.g. in JavaScript / Python, the built-in lists and dictionarys (which are blessed with special syntax) are mutable. > Is the ability to use data/values exclusive to functional programming? No, but expression-orientation makes this less painful > Are monads exclusive to functional programming? You can hack them in by abusing co-routines or perhaps async/await in various languages, but it will never be as good as something built for this purpose. Type-inferences, type-classes and do-notation make monads workable in practice.
- beastman82 2y agoCompletely agree with these. One way to achieve "Make illegal states unrepresentable" is by using "refined" types, a.k.a. highly constrained types. There is a "refined" library in both Haskell and Scala and the "iron" library for Scala 3.
- wesselbindt 2y agoI do not work in a functional language, but these ideas have helped me a lot anyway. The only idea here that I find less directly applicable outside purely functional languages is the "Errors as values [instead of exceptions]" one. On the surface, it makes complete sense, the non-locality of exceptions make them hard to reason about for the same reasons that GOTO is hard to reason about, and representing failure modes by values completely eliminates this non-locality. And in purely functional languages, that's the end of the story. But in imperative languages, we can do something like this: def my_effectful_function(): if the_thing_is_bad: # do the failure thing raise Exception # or return Failure() return Success() and a client of this function might do something like this: def client_function(): ... my_effectful_function() ... and completely ignore the failure case. Now, ignoring the failure is possible with both the exception and the failure value, but in the case of the failure value, it's much more likely to go unnoticed. The exception version is much more in line with the "let it crash" philosophy of Erlang and Elixir, and I'm not sure if the benefits of locality outweigh those of the "let it crash" philosophy. Have any imperative folks here successfully used the "errors as values" idea?
- skirmish 2y agoRust does "errors as values" pretty well, see [1]. You can manually handle them if you want, or just apply the '?' operator to auto-propagate them out of the function. In both cases, it is obvious that there was an error handling needed. [1] https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html https://doc.rust-lang.org/book/ch09-02-recoverable-errors-wi...
- wesselbindt 2y agoI didn't know that! Every time I hear about Rust my opinion of it grows brighter. Thanks for sharing this!
- galaxyLogic 2y agoThe way to do functional programming in imperative languages is to handle the side-effects as high up in the call-chain as possible. That would mean that you return an instance of Error from lower-level and decide in some higher caller what to do about it. That as an alternative to throwing the error. This way you get the benefit of being able to follow the flow of control from each called function back to each caller, as opposed to control jumping around wildly because of thrown errors. In a statically typed imperative language that would need support for sum-types, to be able to return either an error or a non-error-value. Then you would be less likely to ignore the errors by accident because you would always see the return value is maybe an error. Isn't this also basically how Haskell does it, handling side-effectful values as high up as as possible which in Haskell means moving them into the runtime system above all user-code?
- jandrese 2y ago> Make illegal states unrepresentable This is a nice ideal to shoot for, but strict adherence as advocated in the article is a short path to algorithmic explosions and unusable interfaces on real life systems. For example, if you have two options that are mutually incompatible, this principle says you don't make them booleans, but instead a strict enum type populated with only legal combinations of the options. A great idea until you have 20 options to account for and your enum is now 2^16 entries long. Then your company opens a branch in a different country with a different regulatory framework and the options list grows to 50 and you code no longer fits on a hard drive.
- MeetingsBrowser 2y agoThe mistake here is having 2^16 valid options. If you truly do have 2^16 valid and distinct behaviors, it is not possible for humans to correctly write 2^16 different code paths anyway. More than likely, the majority of the combinations of your 29 Boolean flags are invalid. You should strive to have that checked by the program, and not only as a note in external documentation. No one is saying int should be turned into an enum.
- joe_the_user 2y agoThe parent is discussing a situation where you have 16 distinct, binary states many but not all of which are mutually compatible. So you can have 16 bit vector of the states, 16 binary variables or an enum of valid states - the enum would have O(2^16) members because of the distribution of valid states but unlike the others, an invalid state would not be possible to represent.
- jandrese 2y agoYou only have 20 options, but making that many distinctive options is not exactly a stretch. It's not like every single set of options is its own code path, most options represents a small deviation at one particular part of the code. Most of the options aren't mutually exclusive either, only a few combinations are illegal. Imagine a simple shipping system for example. The package might be routed via truck, boat, plane, pipeline, foot, etc... Probably even a combination of those options. The package might be low, medium, or high priority, although high priority packages are not allowed to be delivered by boat. The package might be small, large, or liquid, but liquids can't be delivered by foot. There are 195 different countries with even more regulatory regimes to consider, some of which may have different customs requirements based on mode of transport. Now imagine a problem that is actually complicated. The idea of encoding all of this business logic into the datatype is a road to madness IMHO. Especially if the rules can change on you and require you to rework your datatypes to match. On the small scale this makes a lot of sense and is good advice, but strict adherence is impractical.
- deleted 2y ago[deleted]
- deleted 2y ago[deleted]
- deleted 2y ago[deleted]
- enugu 2y agoFP nerd: The pure core is nice and composable, with the imperative shell at the boundary. State Skeptic: Yes, But! How do you compose the 'pure core + impure shell' pieces? FPN: Obviously, you compose the pure pieces separately. Your app can be built using libraries built from libraries.... And, then build the imperative shell separately. My take is that the above solution is not so easy. (atleast to me!) (and not easy for both FP and non-FP systems). Take an example like GUI components. Ideally, you should be able to compose several components into a single component (culminating in the app) and not have a custom implementation of a giant state store which is kept in something like Redux and you define the views and modifiers using this store. Say, you have a bunch of UI components each given as a view computed as a function from a value and possible UI events which can either modify the value, remain unhandled or configurable as either. Ex: dialog box which handles text events but leaves the 'OK' submission to the container. There are atleast two different kinds of composability (cue quote in SICP Ch1 by Locke) - aggregation and abstraction. Ex: Having a sequence of text inputs in the document(aggregation) and then abstracting to a list of distances between cities. This abstraction puts constraints on values of the parts, both individually(positive number) and across parts(triangle inequality). There is also extension/enrichment, the dual of abstraction. This larger abstracted component itself is now a view dependent on a value and more abstract events. But, composing recursively leads to state being held in multiple layers and computations repeated across layers. This is somewhat ameliorated by sharing of immutable parts and react like reconciliation. But, you have to express your top->down functions incrementally which is not trivial.
- yazzku 2y agoFP is not a silver bullet. GUI is the classic OOP showcase. > Ideally, you should be able compose them several of them into a single app and not have a custom implementation of a giant state If you are suggesting that components store their state, I'm not sure about "ideal" there. That works well for small GUI applications. In GUI applications of modest size, you do want a separate, well-organized and non-redundant data layer you can make sense of, at least from my experience. Qt, specifically, allows you to do both things.
- enugu 2y ago
- munificent 2y agoIt's hard not to giggle when the conclusion right after "Smart constructors" says "Do these ideas belong only in functional programming? While they are practiced more there...". Ah yes, because using constructors to ensure that new objects are in a valid state is virtually unheard of in object-oriented programming.
- wrenky 2y agoOne thing this article does is assume extreme functional mindset, I dont even think OOP enters into the authors mind- With that context, I think that statement isn't about object constructors but type constructors.
- beders 2y agoIn many (but not all) scenarios "Make illegal states unrepresentable" is way too expensive to implement. Especially when dealing with a fast changing domain, having to support different versions of data shapes across long time periods: dynamic data definitions are more economic and will still provide sufficient runtime protection. "Errors as values" - what is an error? I see this pattern misused often, because not enough thought was put into the definition of an error. "Disk is Full" vs. "data input violates some business rule" are two very - very - different things and should be treated differently. Exceptions are the right abstraction in the first case. It's not in the second case. "Functional core, imperative shell" - 100% agreement here.
- 7h3kk1d 2y ago""Make illegal states unrepresentable" is way too expensive to implement." This has not been my experience. The speed increase in development not having to worry about the unrepresentable cases have been very valuable. In addition as requirements change migrating old data hasn't been a huge concern. For code changes refactoring the types helps address new cases as well.
- ffsm8 2y agoThe difficulty of Making illegal state unrepresentable depends entirely on the domain you're working on. And whether discarding the occasional invalid transaction is viable. If you're writing a CMS/wiki software, it's gonna be pretty straightforward to do. If you're working with transactions, trades, contracts etc, it's not.
- fuzztester 2y ago>If you're writing a CMS/wiki software, it's gonna be pretty straightforward to do. >If you're working with transactions, trades, contracts etc, it's not. why not? what's the difference between those two categories, mentioned in your last two sentences, as far as this argument about illegal states is concerned? not clear to me. in fact, just a few weeks ago, I saw a video by yaron minsky of jane street about ocaml. the title of it might have been "why ocaml". I know he has a video by that name. just not sure whether that was the one I saw or another one by him. it can easily be googled. in that video, he talks a fair amount about how jane street uses ocaml, including on how they use it (including defining types for kinds of business domain data, iirc) to make "invalid states unrepresentable" - in fact, iirc, that was the title of one of the slides of his talk. i remember that very well because i was kind of impressed by the idea, although i have not checked it out practically myself yet. but I don't know much about this area, so I'm not saying that either he or you are wrong. just asking for clarification / explanation. also, trades and transactions seems to be the main area that jane street works in. edited for spelling/wrong autocorrect: s/one I show/one I saw/
- LudwigNagasena 2y ago> Errors as values > To me, this simply makes more sense: isn’t it objectively better to get a finite and predictable error value from a function than an unspecified exception that may or may not happen that you still have to guard against? Whether an error is returned as a value or thrown is orthogonal to whether it is finite and predictable. Java has checked exceptions. In Swift you also can specify the exceptions that a function may throw. How is it any less predictable than return values? Semantically, a thrown exception is simply a return value with debug information that gets automatically returned by the caller unless specified otherwise. It is simply a very handy way to reduce boilerplate. Isn't it objectively better to not write the same thing over and over again?
- 7h3kk1d 2y agoI agree with regards to checked exceptions. Unfortunately Java doesn't support any form of polymorphism over thrown exceptions so it makes your code much harder to reuse. In languages that support polymorphic effects I imagine this is less of a concern.
- aiono 2y agoFor "Errors as values", I agree 100% that it's better then special values or untracked exceptions but I also think that current programming languages lack the features that allow encoding errors as values conveniently. Firstly, there is no composition of errors. If I use a library for a network call and then use another library for a database query, now the possible errors should be the union of the errors that can be returned from the either of the functions. But most practical languages lack the mechanism to do that (except OCaml). One has to define a wrapper type just to encode that particular composition. And it won't work if I want to handle for example Not Found case but not Internal Server Error. I see this is because most statically typed languages have nominal typing and not structural typing. But it is a necessity for pretty much any software otherwise people will just see that tracking errors is too much trouble in terms of composition.
- cryptonector 2y ago> I see this is because most statically typed languages have nominal typing and not structural typing. I don't think that's it. I think what's needed is an accumulator method for error interfaces so that you can accumulate new errors into existing errors.
- aiono 2y agoBut this way you don't track what you handled in type level it's only available in runtime. So you are back to untracked errors.
- csours 2y agoMy ideal service layer has a functional core - easy to understand, easy to test. linked from the article: https://www.javiercasas.com/articles/functional-programming-patterns-functional-core-imperative-shell https://www.javiercasas.com/articles/functional-programming-...