Live data from Hacker News

Functional Programming Self-Affirmations

norikitech.com

61–70 of 119 posts

Re: Functional Programming Self-Affirmations

#61

These 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.

I'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…

As someone who's written a pure functional framework for C# [1], I'll bite...

> How can you "Make illegal states unrepresentable" with mutable state and sequences of mutations that cannot be enforced with the type system?

Firstly, don't use mutable state, write immutable types. Secondly, write constructors that reject poorly formed data structures. Thirdly, for existing libraries with types that are mutable, create a wrapper for the library with functions that return an IO/effect monad.

> How can you do "Errors as values" at a large scale without do-notation / monads?

Luckily, from a C# PoV, we have LINQ, which is equivalent to do-notation. I agree that manual management of monadic flow would be hard without something akin to do-notation or LINQ.

You can get quite far with fluent methods, but a general monadic-bind is quite hard to chain if you want to carry all of the extracted values through to subsequent expressions (lots of nesting), so yeah, it would not be ideal in those languages. It should be stated that plenty of functional languages also don't have do-notation equivalents though.

> How can you do "Functional core, imperative shell" without the ability to create mini DSLs and interpreters in your language?

I've never really liked the "Functional core, imperative shell" thing. I think it's an admission that you're going to give up trying to be functional when it gets difficult (i.e. interacting with the real world). It is entirely possible to be functional all the way through a code-base.

In terms of DSLs: I'm not sure I know any language that can't implement a DSL and interpreter. Most people don't realise that the Gang of Four Interpreter pattern is isomorphic to free-monads, so most imperative languages have the ability to do the equivalent of free-monads.

As the GP states, it takes discipline to stick to the constraints that a language like Haskell imposes by default. Not sure about the charisma part!

I have found that having access to a world-class compiler, tooling, and large ecosystem to be more valuable to getting shit done than the exact language you choose. So, bringing the benefits of the pure-FP world into the place where I can get shit done is better than switching to, say Haskell, where it's harder to get shit done due to ecosystem limitations.

There's also the get out of jail free card, which allows me to do some gnarly high-performance code in an imperative way. And, as long as I wrap it up in a function that acts in a referentially transparent way, then I can still compose it with the rest of my pure code without concern. I just need to be a bit more careful when I do that and make sure it's for the right reasons (i.e. later stage optimisations). That's less easy to do in FP languages.

Again, it's about discipline.

If you want to see how this can look in a mainstream, imperative-first, language. I have a few samples in the repo, the one I like to share when helping OO-peeps learn about monads and monad-transformers is this game of 21/Pontoon [2]. I suspect most people won't have seen C# look like this!

[1] https://github.com/louthy/language-ext/

[2] https://github.com/louthy/language-ext/blob/main/Samples/Car...

Re: Functional Programming Self-Affirmations

#62
post #29

Earlier quoted context omitted.

That's merely syntax sugar. Haskell doesn't even have this sugar and so in a monad you have to bind explicitly, and it's fine. It's not a pyramid of doom.

Haskell has do-notation which is a more general kind of this syntactic sugar

Yeah that's what I said about bind.

Let's consider a language like C++ where it has none of those syntax sugars. The absl::StatusOr is a class with the idea that errors should be values. The standard library has std::expected since C++23. So where is your pyramid of doom?

Re: Functional Programming Self-Affirmations

#63

Earlier quoted context omitted.

Where does this exponentional size requirement come from?

Imagine you have a program where there are 4 options, W, X, Y, Z. Y and Z can't be set at the same time, and X can't be set unless W is set. If Y is set then X must be set as well. How do you represent this in a way that makes it impossible, even through programmer error elsewhere in the program, to have the flags in an invalid state? You can create en enum that looks like: enum program_state = ( W_X_Y_NZ, W_NX_NY_Z,…

And the Make Impossible States Unrepresentable crowd program like that?

Re: Functional Programming Self-Affirmations

#64

These 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.

Mostly functional programming does not work (https://queue.acm.org/detail.cfm?id=2611829)

Re: Functional Programming Self-Affirmations

#65
post #44
post #41

FP 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 ea…

FP 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 d…

This is a digression, but regarding OOP, my somewhat provocative view, is that it is not a natural thing, but in most languages, it is atleast 4 different concepts 1. Encapsulation/Namespace, 2. Polymorphism, 3. Extensibility(Inheritance is a special case) 4.Mutability.

These four concepts are forced/complected into a 'class' construct, but they need not be.

In particular, FP only varies on 4, but languages like ML,Clojure do 1,2,3 even better than OOP languages. Modules for encapsulation, Dispatch on first or even all arguments for polymorphism and first class modules, ML style, for extensibility.

Aside: There was a recent post (https://osa1.net/posts/2024-10-09-oop-good.html) (by someone who worked on GHC no less), favorably comparing how OOP does extensibility to Haskell typeclasses, which are not first class, but modules in ML languages can do what he wants and in a much more flexible way than inheritance!

There is also the dynamic aspect of orginal OOP - message passing instead of method invocation, but this is about dynamic vs static rather than OOP vs FP.

What OOP languages have managed to do which static FP hasn't done yet is the amazing live inspectable environments which lead to iterable development like we see in Smalltalk. The challenge is to do this in a more FP way while being modular.

Re: Functional Programming Self-Affirmations

#66
> 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?

Re: Functional Programming Self-Affirmations

#67
post #41

FP 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 ea…

> But, composing recursively leads to state being held in multiple layers and computations repeated across layers.

True, which is why re-frame has a dependency graph and subscriptions that avoid re-computation, i.e. the data dependencies are outside any view tree.

If data changes, only active nodes (ones that have been subscribed to) will re-compute. If nothing changed in a node, any dependent nodes will not re-compute.

It's a beauty.

Re: Functional Programming Self-Affirmations

#68

Earlier quoted context omitted.

Where does this exponentional size requirement come from?

Imagine you have a program where there are 4 options, W, X, Y, Z. Y and Z can't be set at the same time, and X can't be set unless W is set. If Y is set then X must be set as well. How do you represent this in a way that makes it impossible, even through programmer error elsewhere in the program, to have the flags in an invalid state? You can create en enum that looks like: enum program_state = ( W_X_Y_NZ, W_NX_NY_Z,…

Maybe I'm spoiled by TypeScript but this is how I'd do it in a structural typing system:

    type ConstrainedByW =
    | { w: false, x: false }
    | { w: true, x: bool }
    
    type ConstrainedByY =
    | { x: bool, y: false, z: bool }
    | { x: true, y: true, z: false }
    
    type ProgramState = ConstrainedByW & ConstrainedByY

Re: Functional Programming Self-Affirmations

#69

Earlier quoted context omitted.

The 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 in…

You 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, pla…

You don't need a single type to represent the entire program state.

We probably both agree that separate types for shipping methods, priorities, size, country makes sense.

The API can be designed to prevent illegal transitions between types to arrive at an invalid state. The exact implementation depends on the language.

> The idea of encoding all of this business logic into the datatype is a road to madness IMHO

The alternative is hoping that every developer remembers not to violate any of the implicit and unenforced rules.

If the system is too complicated to be represented in code, can a human realistically hold the entire state machine in their head as they make changes?

Re: Functional Programming Self-Affirmations

#70
post #61

Earlier quoted context omitted.

I'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…

As someone who's written a pure functional framework for C# [1], I'll bite... > How can you "Make illegal states unrepresentable" with mutable state and sequences of mutations that cannot be enforced with the type system? Firstly, don't use mutable state, write immutable types. Secondly, write constructors that reject poorly formed data structures. Thirdly, for existing libraries with types that are mutable, create a…

> Not sure about the charisma part!

Software developed on a team where everyone has different values/principles... some times it's not the technical discipline that is required, it's convincing folks to adopt these patterns and stick with them that's the hard lift. :)

Post reply on HN