9 ms·
As a programmer, I don't know if it's still relevant to make a strict separation between programming paradigms. You can use immutable types, pure functions, clo
by yodsanklai 2y ago
As a programmer, I don't know if it's still relevant to make a strict separation between programming paradigms. You can use immutable types, pure functions, closures and so on in most languages. Conversely, you can define mutable types and imperative code in most functional programming languages.
I'm always surprised reading comments on these topics, people saying they don't grasp FP. But don't we use higher-order functions, closures, combinators all the time in most mainstream languages? How hard can it be to learn OCaml or Clojure for someone who use closures all over the place in JS?
Monads have a steeper learning curve, but besides Haskell, they aren't that pervasive. And there are constructs with similar flavor in mainstream languages too (result types in Rust...)
- acchow 2y agoMonads are pervasive: async-await. We just don’t call them monads.
- bazoom42 2y agoHow is async-await monads? Isn’t it just syntax sugar over callbacks?
- ryandv 2y agoConsider async-await a syntactic sugar over Promises (from JavaScript). Then, Promises constitute an instance of the Monad typeclass where monadic `bind` or (>>=) is `Promise.then()`, and `return` is `Promise::resolve()`. Here is a translation of a modification of the example given in [1]: const promise1 = Promise.resolve(123); promise1.then(v => v * 2).then((value) => { console.log(value); // Expected output: 246 }); into Haskell: ghci> let promise1 :: IO Int = return 123 ghci> promise1 >>= (return . (* 2)) >>= print 246 One key discrepancy worth pointing out is that in the `Promise.then()` API of JavaScript, the function provided to `then` (e.g. `v => v * 2` above) is implicitly composed with a call to `::resolve` in order to turn that function's pure return value, the `Number` 246, into a Promise resolving into the `Number` 246; in Haskell, this operation must be made explicit (hence the composed function `return . (* 2)` passed to the first application of (>>=) or `bind`). You could say that the instance method `Promise.then()` expects an argument of type (a -> b), with the return value of type `b` being implicitly and automatically wrapped into a Monad `m b`, whereas Haskell's bind expects an argument of type (a -> m b) on that operator's right-hand side, with the return value explicitly wrapped into a Monad `m b` by the provided function argument itself. [0] https://wiki.haskell.org/Monad https://wiki.haskell.org/Monad [1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- bazoom42 2y agoMaybe I’m confused, but I dont see how Promise.then() corresponds to bind? If I understand correctly, the point of the bind function is you pass a callback which itself return the monad type. But the Promise.then() callback should not return a monad but just the regular result value of invoking the callback. So in essence Promise.then() is like Array.map() while bind is like Array.flatMap() Edit: It seems you are correct, you can indeed return a promise in the callback and it compose correctly. But in your example the callback does not return a promise and is therefore not an example of monadic bind.
- ryandv 2y agoI had a caveat in my original post re. the implicit wrapping of the `Promise.then()` callback's (pure) return value in a new Promise, and how this differs from Haskell's monadic bind; I had hoped to make a loose analogy while pointing out the differences for the sake of illustration. However it is indeed also possible to return a Promise from `.then()`'s callback, which is closer to Haskell's bind: [0] The behavior of the returned promise (call it p) depends on the handler's execution result, following a specific set of rules. If the handler function: * returns a value: p gets fulfilled with the returned value as its value. [...] * returns an already fulfilled promise: p gets fulfilled with that promise's value as its value. ... then you can obtain a solution closer to the Haskell translation by using the behaviour of the second cited bullet point from the MDN article: const promise1 = Promise.resolve(123); promise1.then(v => Promise.resolve(v * 2)).then((value) => { console.log(value); // Expected output: 246 }); [0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- bazoom42 2y agoThis article argues that while promises support the required operations for monad, they do not support the monad laws: https://www.siawyoung.com/promises-are-almost-monads/ https://www.siawyoung.com/promises-are-almost-monads/
- deleted 2y ago[deleted]
- wk_end 2y agoAt that moment, the student became enlightened.
- bazoom42 2y agoCallbacks are not the same as monads if that is what you are implying.
- mrkeen 2y agoThat's right, but only because it's a 1-to-many comparison error. Callbacks are one particular monad, not monads in general: https://jsdw.me/posts/haskell-cont-monad/ https://jsdw.me/posts/haskell-cont-monad/
- dumbo-octopus 2y agoBut then CPS machinery is often used to implement Monads. > Programming with monads strongly reminiscent of continuation—passing style (CPS), and this paper explores the relationship between the two. In a sense they are equivalent: CPS arises as a special case of a monad, and any monad may be embedded in CPS by changing the answer type. https://cs.uwaterloo.ca/~david/cs442/monads.pdf https://cs.uwaterloo.ca/~david/cs442/monads.pdf
- bazoom42 2y agoPromises in JavaScript are not monads as far as I can tell. While they have a somewhat similar interface, they do not appear to obey the monad laws.
- explaininjs 2y agoThere is one particular edge case in which they do not satisfy the laws. That happens to make them much more practical in day to day coding than a strict interpretation would be.
- bazoom42 2y ago
- vilunov 2y agoAsync-await in Rust isn't a Monad.
- fire_lake 2y agoMainstream languages are not expression orientated like true FP languages are. Most people working in mainstream languages aren’t aware of the significance of this and wonder why FP seems awkward in their language, despite it having closures, some immutable types, etc.
- __MatrixMan__ 2y agoOne thing that struck me while learning nushell is that instead of: $ echo "hello world" hello world I can do: $ "hello world" hello world Is this an indication that it is expression oriented? (just checking that I've understood the phrase).
- ryandv 2y agoThis is the statement/expression distinction; `echo "hello world"` is a statement that, when evaluated, has the side effect of printing "hello world" to stdout (or wherever), and has no value (for the purposes of this example). `"hello world"` is an expression with the literal string value "hello world"; your REPL probably prints out that expression's value once it is evaluated for convenience. FWIW I'm not actually familiar with nushell and am speaking fairly generally about statements vs. expressions.
- acchow 2y agoYes. Essentially, statements have no value (or return no value). Expressions have a value.
- peterbecich 2y agoI will add to that the other big difference is how much the compiler enforces the style.
- SatvikBeri 2y agoI wouldn't think of paradigms as strictly separate, but there are definitely clusters of related techniques that go well together. Higher order functions work best with immutability and pure functions, and struggle without them. Currying is somewhat useful by itself, but much more valuable with function composition or HOFs. It's also important to teach the techniques together because functional programming tools are typically (deliberately) less powerful, which makes them easier to analyze, but means you need more tools.
- smrtinsert 2y agoMost js code bases I see (frontends typically in react) have only a basic sense of functional programming/closures. It is a massive paradigm shift to move to clojure from where modern js is today. It was probably less so in the jquery days funny enough
- kelnos 2y agoI don't think we should think of things as having a strict separation, but certainly some languages push you harder than others toward certain programming paradigms, and some make other paradigms difficult or awkward to use. For example, while I can do FP in Rust, I would not really call Rust a FP language. It does have some features that make doing FP possible, but a lot of the code I see (and write) is a mix of imperative and FP. But if I'm writing Scala, I'm going to be mostly writing FP code, and the language helps me there. Recent versions of Java make it easier to write FP code if you so choose, though you'll still see a lot of people write more imperative code in Java. (I think if Rust had Scala's for-comprehensions, I'd write more FP-ish Rust. I know there are crates that emulate them, but they're not the most ergonomic, and I don't want to write what ends up being unidiomatic Rust code.)
- vilunov 2y agoFor-comprehensions are actually not functional programming, their raison d'etre is to embed traditional imperative programs in expression-based referenetially transparent FP programs. I think if Rust had Scala's for-comprehensions, it would be a strictly worse language than it is now. FP langs' do- and for-comprehensions are highly unergonomic, not least due to all the ways it could be overloaded. Haskell has tons of extensions, Scala's is basically desugaring to flatMap calls. This has a lot of abuses in the wild, and with how it depends on implicits causes practical problems when you write that code. Scala's new wave of direct syntax and general direction from Monads to algebraic effects is more sane in how it presents compiler messages, and opens ways to have better performance.
- roenxi 2y agoThere is an interesting trend where things with a strong mathematical definition tend to have the advantage. "Functional Programming" doesn't have a precise definition at all as far as I know, so it is likely it doesn't refer to a real thing. Most of the paradigms are similar as they don't seem to actually mean anything. People seem to want to describe something (in today's article, data flow) but they don't quite have the language to do it. If you go to https://en.wikipedia.org/wiki/Functional_programming https://en.wikipedia.org/wiki/Functional_programming right now you will see "functional programming is a programming paradigm where programs are constructed by applying and composing functions" which is a bit of a ... it is quite hard to program without doing that. Even SQL is basically applying and composing functions. There are languages that are branded together because they have similar communities or techniques, but the brand isn't describing something beyond people thinking that things belong in a basket together.
- culi 2y agoIt's funny you say that because I'd say FP has a much more well defined mathematical foundation compared to OOP. In fact, before it became as dominant as it is today, it was often criticized as being too scholarly and theoretical. It's built mostly on the foundations of Lambda Calculus (and maybe some Type Theory). I don't think OOP ever had nearly as strong of a mathematical/scholarly foundation as FP has
- roenxi 2y ago> It's built mostly on the foundations of Lambda Calculus (and maybe some Type Theory). Can you describe a programming language that isn't based on lambda calculus? It is a universal model of computation.
- LegionMammal978 2y agoIn that sense, every programming language (as well as lambda calculus) is based on Java, since Java is a universal model of computation. So I don't think it's very useful to take "is based on" to be as broad as "is interpretable using".
- majoe 2y agoTrue, the conceptual difference of (pure) functional and imperative programming is disguised by the many functional patterns most mainstream languages have absorbed. While these patterns are useful, there is more to say about pure functional programming. I recently gave a talk to some colleagues about that, which was divided into two parts: Practical functional programming patterns we can use today in our codebases (we use mainly C++, Python) and a more abstract part about pure functional programming. The first part basically boils down to using functions as first class "objects", while the point of the second part was, that there can't be implicit state in pure functional language. The consequence of that is, that there is no strict order of execution, which is in direct contrast to imperative programming, which is all about list of statements, that are executed one after another. I presented small code examples in Haskell and showed corresponding execution graphs to emphasise, that the compiler can easily optimise the execution order. I like that POV, because it clearly distinguishes imperative from functional programming. Starting from there, it's also easy to understand the motivation behind monads or elaborate on architectural patterns like " functional core, imperatively shell".
- Reefersleep 2y agoI don't understand the bit about the execution order, nor how it's relevant in your day-to-day programming.
- mbivert 2y agoWhen you execute a function in a mainstream languages, the arguments are usually evaluated first, then the function is called with the result of that evaluation. E.g. the following in JS will always print the confirm message. But in a lazy language like Haskell, as the x parameter is never used, it'll never be printed. (function(x) { return "bar"; })(confirm("foo")); This can help with implementing things like infinite streams (e.g. iterate[0] in Haskell), which may be a pleasant patterns to solve some problems sometimes. On this example alone, I wouldn't qualify it as highly relevant for day-to-day work, but there may be more interesting use cases I'm not familiar with (I'd guess there might be interesting side-effects in concurrent settings). [0]: https://hackage.haskell.org/package/base-4.20.0.1/docs/Prelude.html#v:iterate https://hackage.haskell.org/package/base-4.20.0.1/docs/Prelu...
- fulafel 2y ago"People who closures all over the place in JS" have IME much to gain in learning about and using FP. - understanding paradigms of state handling enables you to make conscious choices about what to aim for, how to design your data model, etc - learning the benefits of having all data as values, except at the edges of the system (instead of object refs encompassing mutable state and/or IO handles) - eg the resulting ability to reason about your system in a principled way (this can still be hard to mentally reach, even given all the ingredients, if you're very used to the "poking the mystery ball" way of relating to your app) - how it comes together so you can just test a subset of your code at your REPL, being able to paste the input datastructure there (because it's just values, not active objects) without having your app framework up and running in a stateful context in browser or server environment. Another strength of FP is about paralell programming. The bottleneck of parallelizing code by using multiple threads is concurrency bugs which come from mutable data. Immutable data and referential transparency is close to a silver bullet for this. (But JS programmers aren't as likely to appreciate this since JS is nearly always single-threaded)
- wruza 2y agothey don't grasp FP. But don't we use higher-order functions, closures, combinators all the time in most mainstream languages? We don’t really use them in mainstream languages. They exist as idioms for shallow application, but one doesn’t go full-on combinators in javascript, for at least performance reasons. What we do in mainstream is as FP as walking stairs compared to rope climbing. That said I think deep FP makes no sense and only entertains bored intellectuals by being a subspecies of code golf.
- mrkeen 2y ago> I don't know if it's still relevant to make a strict separation between programming paradigms. Consider whether the same input to your code will result in the same output (aka functions), that's a good place to draw the line. You can stick 'FP' fancy types and closures into a method; that doesn't turn it into a function.