Live data from Hacker News

Functional Programming Self-Affirmations

norikitech.com

81–90 of 119 posts

Re: Functional Programming Self-Affirmations

#81
post #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-compu…

Doesn't skipping view layers mean that constraints held by intermediate layers can be violated?

Say a city stats(location, weather) component is held inside a region component which in turn is in charge of a product route generating component (which also contains a separate 'list of products' component).

You can't update the city coordinates safely from the top as the region component enforces that the cities are within a maximum distance from each other. The intermediate constraint would have to be lifted to the higher level and checked.

Edit: There is also a more basic problem. When your app has multiple types of data(product, city), the top level store effectively becomes a database(https://www.hytradboi.com/2022/your-frontend-needs-a-databas...). This means that for every update, you have to figure out which views change, and more specifically, which rows in a view change. This isn't trivial unless you do wholesale updates (which is slow), as effects in a database can be non-local. Your views are queries and Queries on streaming data is hard.

The whole update logic could become a core part of your system modelling which creates an NxM problem (store update, registered view -> does view update?). This function requires factoring into local functions for efficient implementation which is basically the data dependency graph.

Re: Functional Programming Self-Affirmations

#82
post #71
post #54

In 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 en…

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

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

Re: Functional Programming Self-Affirmations

#83

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…

Maybe not in literally every language, but, to cherry pick some examples: Java (along with many other object-oriented languages) lets you create objects that are effectively immutable by declaring all fields private and not providing any property setters or other methods that would mutate the state. Errors as values is one of the headline features of both Go and Rust, neither of which has do notation and monads. Func…

I consider Rust's Result and Option to be monads. Is this incorrect?

Re: Functional Programming Self-Affirmations

#84
post #37

Earlier quoted context omitted.

Nobody says you have to have a single enum type containing all the combinations. Chances are, you can use sum types (discriminated unions) to factor things nicely if you think about them. For example if option B is only relevant when option A is set to true, you can have something like data OptA = ATrue OptB | AFalse data OptB = BTrue | BFalse There are three valid combinations but no type has three alternatives. Nob…

Nobody says you have to have a single enum type containing all the combinations. No, no one would continue up to 2^16 and the code would get unmanageable long before that. But it's illustration of the problems starting out dealing with the invalid states of two variables using an enum because what happens when more and more variables arrive? Sure, the standard answer is "just refactor" but my experience is no client…

> and a trickle of binary conditions is a very common occurrence as is code expanding to handle these (and become excessively complicated).

But you still have to handle this in your code. Wherever you have your conditions that handle this, your nest of if statements still need to cover all of these invalid combinations and ensure your app doesn't silently do the wrong thing or just crash (better).

Changing requirements requires changing code. I don't think it's a valid argument to say "we shouldn't take that approach because as requirements change we'll have to change the code". That's essentially software development.

Practically if you don't want to use enums and want another option, use a "builder" object. Pass in all of your booleans there and have it do you validation when you call its build method. It returns a read only configuration that the rest of your system can use, and the build method fails if an invalid combination of flags are passed in.

Again you force only valid combinations to exist after you call "build". And all code relies on the config produced by the build method.

Re: Functional Programming Self-Affirmations

#85
post #37

Earlier quoted context omitted.

Nobody says you have to have a single enum type containing all the combinations. Chances are, you can use sum types (discriminated unions) to factor things nicely if you think about them. For example if option B is only relevant when option A is set to true, you can have something like data OptA = ATrue OptB | AFalse data OptB = BTrue | BFalse There are three valid combinations but no type has three alternatives. Nob…

Imagine a case where you have 4 options. W, X, Y, Z. Y and Z are mutually exclusive. X can only be set if W is set. If Y is set then X must be set. Going down this road you end up encoding your business logic into your datatypes. Which is good to a degree, but makes things messy when new options are added or requirements change. Imagine a new option U is introduced that is only valid when W is unset and Z is set but…

This is an instance of inherent complexity. Your domain is complex. You either place them into a series of nested if statements (which is what majority of programmers do), or you place it into the type system. You cannot avoid complexity either way. We are merely arguing where this complexity belongs. Such complexity is hard to manage in either case.

Re: Functional Programming Self-Affirmations

#86

Earlier quoted context omitted.

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

Right, I understand. But my question is, how do you _ensure_ a failure value is dealt with by clients? In purely functional languages, your clients have no choice, they'll have to do something with it. In imperative languages, they can just ignore it.

In Rust, there's a `#[must_use]` attribute that can be applied to types, such as Result, and on functions. This triggers if the return value is not used. It's only a warning though, but you could imagine a hypothetical imperative language making this a hard error

Re: Functional Programming Self-Affirmations

#87

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 )

I have a lot of respect for Erik Meijer and I agree with the basic premise of the paper/article. However, I don't fully agree with Erik's position.

Let's say this was my program:

    void Main()
    {
       PureFunction().Run();
       ImpureFunction();
    }
If those functions represent (by some odd coincidence) half of your code-base each (half pure, half impure). Then you still benefit from the pure functional programming half.

You can always start small and build up something that becomes progressively more stable: no code base is too imperative to benefit from some pure code. Every block of pure code, even if surrounded by impure code, is one block you don't have to worry so much about. Is it fundamentalist programming? Of course not. But slowly building out from there pays you back each time you expand the scope of the pure code.

You won't have solved all of the worlds ills, but you've made part of the world's ills better. Any pure function in an impure code-base is, by-definition: more robust, easier to compose, cacheable, parallelisable, etc. these are real benefits, doesn't matter how small you start.

So, the more fundamentalist position of "once one part of your code is impure, it all is" doesn't say anything useful. And I'm always surprised when Erik pulls that argument out, because he's usually extremely pragmatic.

Re: Functional Programming Self-Affirmations

#88

Earlier quoted context omitted.

Could 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?

~~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-va... 2. M…

Yep, 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 ;(".

Re: Functional Programming Self-Affirmations

#89

Earlier quoted context omitted.

Maybe not in literally every language, but, to cherry pick some examples: Java (along with many other object-oriented languages) lets you create objects that are effectively immutable by declaring all fields private and not providing any property setters or other methods that would mutate the state. Errors as values is one of the headline features of both Go and Rust, neither of which has do notation and monads. Func…

I consider Rust's Result and Option to be monads. Is this incorrect?

Depending on what functions are in there, they are. But you can make types that happen to be monads in C, too. All you need is a datatype with `return` and `bind` functions that follow a certain spec.

What makes Haskell different is that it has a language-level concept of a monad that's supported by special syntax for manipulating them. (C# does, too, for what it's worth.) Without something like that, observing that a certain type can be used as a monad is maybe more of a fun fact than anything else.

(ETA: for example, many, many languages have list types that happen to be monads. But this knowledge probably won't change anything about how you use them.)

Re: Functional Programming Self-Affirmations

#90

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…

"Make illegal states unrepresentable" can be done by encapsulating the variables inside a single data object(struct/class/module) and only exporting constraint respecting functions. Also, Algebraic Data Types can be present in FP/non-FP languages.

The Result monad can be implemented in any static language with generics (just have to write two functions) and in a dynamic language this is easy (but return will have to be like T.return as there is no implict inference).

I didn't get the relation between FCore/IShell and DSLs, the main requirement for FCore is a good immutable library. Macros help DSLs though that is orthogonal.

But really, my main point is that OOP vs FP is red herring as 3/4 aspects which characterize OOP can be potentially done in both OOP and FP, with different syntax. We shouldn't conflate the first 3 with the 4th aspect - mutability.

An OOP language with better extension mechanism for classes +immutable data structure libraries and a FP language with first class modules would converge. (ref: Racket page below and comment on Reason/OCaml down the page).

See Racket page on inter-implementability of lambda, class, on the unit(ie. a first-class module) page here (https://docs.racket-lang.org/guide/unit_versus_module.html). Racket has first class 'class' expressions. So, a mixin is a regular function.

Post reply on HN