8 ms·
Why do you need all this theory when you can use simple imperative languages? for staff in company.staff: for pet in staff.pets: if pet.type
by vortico 6y ago
Why do you need all this theory when you can use simple imperative languages?
for staff in company.staff:
for pet in staff.pets:
if pet.type == 'cat':
print(pet.name + " belongs to " + staff.name)
I don't understand why functional languages are used at all.
- mgraczyk 6y agoThat's more difficult to type than something like `.company.staff | .pets | flatten | filter .type == "cat" | log(...)`
- vortico 6y agoMaybe so, but much easier to read. Code is read 10x more often than written, especially in teams, so it makes sense to optimize for legibility rather than elegance in some "higher" mindset, or less keys pressed (because most of the time will be spent staring at the line you wrote than actually typing if you write a functional line like that.)
- kccqzy 6y agoThat's a bad argument because you didn't specify who the audience is. Functional code is more readable to me (and many teams I've worked with), imperative code is more readable to you and that's fine. Go with your team's preference. And, most of time, what your team prefers can easily be changed with a few sessions to introduce them to a functional style.
- akvadrako 6y agoI totally disagree. I can read the short version pretty quickly and get an idea what it does - every word matters. The original version with for loops just looks like a block of text. Spacing would help, but I would still need to look carefully to pick out the important bits.
- marta_morena_29 6y agoIt's a tradeoff. The problem is that academia often comes from the angle of abstracting and generalizing everything, which is even more harmful than copy & pasting and using the simplest code possible (mostly because copy & paste + simplest code is still readable and can be improved upon, while academic style code usually is inaccessible). You don't want to listen to any specific camp. The best is always in the middle and you are on the right track: "Pick what is easy to read." However there is a caveat, which is that functional style is only hard to read at the beginning. Have a look at how modern languages integrate functional programming (see java, C#, C++, etc.). Java streams are a common example. They make most code much easier to read ONCE you are used to it, because they provide standardized behaviour that is side-effect free. When you write everything in plain syntax, the reader needs to abstract your code in his head, while when using streams, you just know what `filter`, `map`, `reduce`, etc. do and you only need to look at the small customizations. It's easier to read once you are used to it. However there is a limit and this limit get hit quickly. A lot of people like to make their code as complicated and fancy as possible and reduce every possible line. This is definitely wrong. Code must be easy to understand and change. If you fail that, you are doing it wrong. This also depends on your environment. If your co-workers don't understand the code you write, you have failed, no matter if your code is "nicer" or not. You either need to write simpler code or educate your co-workers to create common ground.
- elcritch 6y agoPerhaps its easier for you to read. It takes me 2-3 longer to read the loops since each loop is unique and I have to parse the semantics/context, whereas its quick for me to parse the behavior from the named functional operators even at a glance. Conversely I find writing the functional version a bit trickier for complex reduce operations.
- rrdharan 6y agoForget it, Jake. It’s Chinatown.
- MrBuddyCasino 6y agoUnderrated comment.
- vortico 6y agoWait a second, you left out the most important part: What's in the `log(...)` function? How do you get the name of the pet and the name of the staff in that line if they have the same accessor `.name`? That's my point. The fact that you didn't include it implies to me that it was too difficult to figure out, and a true implementation would be far more complicated, so it's not a fair comparison.
- mgraczyk 6y agoI disagree, there are many ways to solve this problem. for example .company.staff | .pets | flatten | filter .type == "cat" | print(.name + " belongs to " + ..name)
- choeger 6y agoFunctions are composable. Your statement is not. If I would ask you to create a snippet that searches for cats, another one that searches for dogs and a third one that searches for staff that owns both a cat and a dog you will either duplicate a lot of code or end up with functions. Now if you restrict yourself to not introduce any side effects, the interpreter can be proven correct, the execution can be analysed much better and you can have some neat safety guarantees. That's why.
- vortico 6y agoWhat's wrong with functions? A function `printCatsBelongingToStaff()` is much easier to read than a line of functional code. I don't understand what you mean by "analyzed much better" and "neat safety guarantees". Is my code hard to analyze or unsafe?
- choeger 6y agoYour code is impossible to analyze. Console.log is a side effect. For loops can be easily mapped into functional constructs, but not your snipped does not compute anything. It's not a function.
- vortico 6y agoOh, you mean using analyzing software. Yeah, I've never had a use for that. On the other hand, you could make my function append to a string and then return the string. Then it wouldn't have side effects so it would be analyzable.
- dragonwriter 6y ago> On the other hand, you could make my function append to a string and then return the string. Then it wouldn't have side effects State mutation is a side effect. If you mean it builds it up locally, it would then not have nonlocal side effects (e.g., it would have a pure functional interface even though it has an imperative implementation). But to do that, you'd have construction and mutation overhead in the code, whereas with functional idioms / comprehensions, you would not. This is especially the case when (as is often the case in practice) you are taking collection datastructures and producing collection structures to work with.
- kccqzy 6y agoTo me the biggest advantage is expression. You can package up a whole traversal or other optics into a single expression and then you can give it a name, refer to it, augment it. For example what if later you'd like to add another `if` into the inner loop? Would you duplicate the code? Would you add a callback to the original imperative code? The big idea here is that access to some subpart of a big object (like a path) can be abstracted into its own expression type and made composable, and the less important idea is that it can also be well-typed. The second advantage is conciseness. I don't know if you've actually used jq, but if you have, I really don't know why anyone prefers a verbose for-loop-and-if-statement block over a single concise jq expression. Paul Graham expressed this idea better than I could have so read this: http://www.paulgraham.com/power.html http://www.paulgraham.com/power.html
- deleted 6y ago[deleted]
- hjntmp 6y agoBecause it is easier to reason about data transformations. Here when I see loops and conditionals it is confusing because anything could be happening. company.staff.filter(notCats).forEach(printMessage) has more information of what is going on for example imho. But there is a lot more to it.
- vortico 6y agoHow would you define the `printMessage` function? The `pet` object doesn't have context about `staff`, and the `filter` function is filtering staff, not pets. It could return staff that have both cats and dogs, so it would incorrectly print dogs.
- hjntmp 6y agoIt was a rough example but the idea is thinking in terms of data in and data out and how can it be done. Here the train of thought first would be: do I have the right data form for the thing I am doing? Here we have: {company: {staff: [{..., pets: []}]}} And what we want to do is to produce a list of all the pet cats with its owner name. [{cat: "bla", owner: "bla"}...] or [{owner: "bla", cats:[...],...}, ...] So I guess what Im trying to explain is that what is important is the change in the way of thinking about problems. When you think of them as data transformations there is a whole lot of possibilities that open. And it is not more expensive because you even have things like transducers. When you abstract away the iteration you are able to compose much more easily. And to answer your question directly in my example the print function would print for each cat belonging to the staff which is not great flattening would be more elegant. What I wanted to convey is thinking in data transformations and abstracting away the implementation details of the iteration.
- hjntmp 6y agoIn javascript: const stf = [ {name: "x", pets: [{type: "cat", name: "kitty"}, {type: "cat", name: "kitty2"}]}, {name: "y", pets: [{type: "dog"}]}, {name: "z", pets: [{type: "cat", name: "miau"}]}, ]; const myTransform = ({ pets, name: ownerName }) => pets .filter(({type}) => type === "cat") .map(({ name: catName }) => ({ ownerName, catName })) stf .map(myTransform) .flat() .forEach(({ownerName, catName}) => console.log(`Cat ${catName} to ${ownerName}`))
- renewiltord 6y agoBecause I can abstract over whether it's staff.pets or staff.children etc. etc. It's all right. In the sense of using a tool when you find yourself constrained perhaps it's worth waiting till you find a problem where you feel "there's got to be a better way". It's happened to me when I was writing generators/shrinkers for generative tests that operate over any Thrift or Protobuf type.
- qppo 6y agoThe syntax you just typed is an example of the theory in practice. Syntax != semantics.
- dragonwriter 6y agoFunctional idioms are often more expressive when you want to do something with a result other than an immediate side effect like printing. Sure, you can use imperative loops to build up a data structure to work with, but functional idioms (or comprehensions, which are generally an imperative-looking wrapper over functional idioms) can be quite concise and expressive for that purpose. for (owner <- company.staff) for (pet <- staff.pets if pet.type=="cat") yield (owner.name, pet.name) builds a datastructure with the same information yours prints, with no extra boilerplate around building the datastructure.
- spullara 6y agoi would say this is just imperative programming.
- slaymaker1907 6y agoHow would you abstract code so that you can also update pet's name in addition to the above statement while minimizing duplicate code? I think that is one of the key things about lenses.
- ben509 6y agoThe theory lets compilers catch a ton of bugs at compile time. When you can get a functional language to type check, it really does Just Work. And when you limit side-effects, you know another function isn't going to monkey with something it's not supposed to. That said, I'm presently struggling with AST transformations and other fun stuff in Haskell, so I completely agree with stating it imperatively. It's far, far easier to learn and read simple imperative code. That's why in my language Tenet[1] I'm trying a hybrid approach: the underlying representation is immutable, but the language uses ordinary control flow and mutation. [1]: https://tenet-lang.org/ https://tenet-lang.org/
- synthc 6y ago>When you can get a functional language to type check, it really does Just Work. Not all functional languages have static typing. Also type checking helps, but saying that if the types check out it just work it pushing it IMO. No type checker will catch this error: sqrt :: double -> double sqrt x = x sqrt 10
- AnthonBerg 6y agoDependent types are able to express the constraints that prevent or catch this error. The types of the arguments to the function can have value constraints on it, and those constraints can be determined from a value that exists there: the name of the function.
- AlotOfReading 6y agoNot only do dependent types have huge decidability issues, it's my understanding that integrating them with side effects is still an area of active research.
- AnthonBerg 6y agoI think fortunately those are orthogonal matters :) First, I don't know that dependent types have huge decidability issues that are an inherent obstacle in general; Idris for one has a very practical and elegant model of computation – things like totality checking, linearity rules and elimination of "scaffolding" types do a lot for us. Dependent types do allow telling the compiler about more dimensions to consider – in a way that allows for a computational explosion at compile time – but those were always an available to consider as part of the computation; Dependent types dont add that complexity, rather they allow us to address it in the type system. I see it such that we're in a stronger position to manage and navigate that complexity by allowing us to express it to the compiler in a succinct and clear way near the core context of desired action. Dependent types allow us to do less by allowing us to do more. And integrating purely functional mechanisms with side effects is a fun and interesting avenue of research, and I don't know that adding dependent types makes that more difficult; I'd think it makes it easier?
- specialist 6y agoI like both imperative and functional style programming. They're isomorphic if composition is favored. Imagine interpreters iterating over ASTs. Easy peasy. I strongly dislike hybrids, multiparadigm, metaprogramming, and inheritance-heavy OOAD. Most of our work is CRUD, meaning data centric. Right? My arm wavy overgeneratalization is that functional programming promotes functional decomposition style programming, which emphasizes flow of control modeling over data modeling. Especially when noobs go overboard with lambdas, stream processing, reducers, and so forth, which obfuscates the data transformations. Whereas imperative programming can be more straightforward for simple data processing. Until noobs go overboard with OOAD stuff like inheritance, making wrapper classes for everything, and design pattern overkill. Then the wheels fall off and the engine explodes once someone reads a book about inversion of control, dependency injection, mocking, aspects, or optionals (non-null). YMMV.
- matt-noonan 6y agoThe point is really that lenses are values that represent locations in a data structure. And, as values, they can be combined, transformed, serialized, etc etc. Imagine having a type that represents a chain of method selectors, and that gives you some idea of the purpose. The fact that method selectors only appear very rarely as first-class values in most languages means that most people aren’t tuned in to scenarios where they could be applied. But I bet you’ve invented special cases of this yourself, when you had a function that needed to dig data out of one of several locations, depending on other inputs.
- contravariant 6y agoThat exact syntax works for pretty much any Monad. You need the category theory to avoid reinventing the wheel every time.
- chmln 6y agoBecause of "composability", even if the highly coupled functions will likely never be reused. And leaky "abstractions" that create more problems than they solve but look cooler than doing things the straightforward way.
- fulafel 6y agoThat's perfectly fine functional code you just wrote. Type system trickery can also be FP but a lot (most of?) practical FP code is written in dynamic FP languages like Clojure and Erlang and Elixir, or just Python, TypeScript, etc.
- vnorilo 6y agoSimple? I know it's easy to forget how much .. stuff .. there is in an imperative language. You have the side-effect in print, and if you replace that with appending a string you're going to invoke the weird and wonderful assignment operator. It's easy to forget how strange these things (state and side effects) are once you have mastered them in your craft. The reason I love functional style with immutable data is because it is so dead simple and easy on my tired brain.
- AnthonBerg 6y agoOptics allow us to do something like this: import optics.for as o_for o_for company.staff.pets as (.{pet}, optics.parentNode as owner) where (pet.type == "cat"): print "{.name|%s} belongs to {owner.name|%s}") We're guaranteed to be safe against null values in company and staff and pets – we don't have to think about it – and no "for" or "if" is required for walking down the structure. Also the types of pet and owner are completely certain, and we can give verified compile-time guarantees that the string we're populating is 100% correct to match the input. This is the very simplest use of optics. Optics also allow us to do express wild things over those structures, such as selecting nodes based on some defined statistical relationship to the statistics of defined parts of the whole structure. It's as simple and concise as the example above. And as efficient as possible.