4 ms·
Okay, I get the concept. Where is the argument for it? >Sometimes, especially in abstract situations involving higher-order functions, providing names for tang
by ibejoeb 9y ago
Okay, I get the concept. Where is the argument for it?
>Sometimes, especially in abstract situations involving higher-order functions, providing names for tangential arguments can cloud the mathematical concepts underlying what you’re doing.
Sometimes? When?
>point-free notation serves as an example of how subtle changes to the way you think about or define your code can have big impacts on readability.
In the end, we're defining `atLeastTwo` and using it identically, so it would be much more compelling if there was an example in which things were, in fact, more readable. This example is just syntactic. It's like arguing that `const atLeastTwo = (x) => x >= 2` is better than `function atLeastTwo(x) { ... }`
> It’s your job as a developer to be cognizant of these tradeoffs.
It's just that this piece isn't really helping me get there.
I think the author is suggesting that we define `atLeastTwo` in terms of an `atLeast` primitive, i.e., currying and partial application. It's clear that this is going to make composition easier, but it is relying on it. Now, I'd like to see how point-free, as a paradigm, yields better results in terms of readability or error prevention.
- fny 9y agoAside from this, there's a higher performance and maintenance cost. Performance since you've turned a single function call into two. Maintenance because now you need to test this `atLeast` primitive when odds are YAGNI.
- mattnewport 9y agoPerformance shouldn't be an issue if you have a decent optimizing compiler and even in JavaScript I would think a decent runtime should JIT away the overhead. Tangent but this is one reason C++ works nicely with a more functional style with lambdas - the compilers are pretty good about optimizing away any overhead resulting from function composition etc.
- fiatjaf 9y agoGo argue with Haskell benchmarks. Functional programming is slow.
- mattnewport 9y agoHaskell is not slow because of point free style. C++ generally won't suffer any performance penalty for point free style in an optimized build. If you think you have specific examples otherwise I'm happy to discuss them.
- lmm 9y agoHuh? Haskell does very well in the shootout, better than JavaScript and often comparable with C.
- mrkgnao 9y agoI won't comment on Haskell's speed here, but function composition can't possibly be making Haskell less faster. The reason is simple: the compiler inlines the composition operator in the vast majority of (probably all?) cases, since the Haskell standard library source code marks it with an INLINE pragma: -- | Function composition. {-# INLINE (.) #-} (.) :: (b -> c) -> (a -> b) -> a -> c (.) f g = \x -> f (g x) http://hackage.haskell.org/package/base-4.10.0.0/docs/src/GHC.Base.html# http://hackage.haskell.org/package/base-4.10.0.0/docs/src/GH....
- KirinDave 9y agoWhat? Haskell's competitive with everything but hand-tuned C code, consistently. In a world where Python and Javascript are credible languages to orchestrate numeric computing, it seems ludicrous to imply that Haskell, which has superior code generation to these options in nearly every particular, is somehow "too slow."
- fiatjaf 9y agoP.S.: I love Haskell too.
- lmm 9y agoJust taking the first examples from the Haskell wiki: sum = foldr (+) 0 foldr (f . g) e rather than sum' xs = foldr (+) 0 xs foldr f' e where f' a b = f (g a) b The idea is that you define loads of tiny functions at higher order, like the sum example, and these are very simple and easy to maintain because every function is a one-liner. I don't know how to respond to the complaint that the example is just syntactic, because it's fundamentally a syntactic construct (at least unless we get to the point of e.g. summing a list of functions to get their composition): the pointfree style is completely equivalent to the pointed style, it's just argued to be a less noisy syntax, that therefore increases readability and maintainability.
- vanderZwan 9y agoConcatenative languages come to mind. You basically don't have to think about partial application ever again, because you get it "for free" (at the cost of reverse Polish notation). Forth, Joy, Factor and Kitten are all examples of languages based on this paradigm. [0] https://en.wikipedia.org/wiki/Forth_(programming_language) https://en.wikipedia.org/wiki/Forth_(programming_language) [1] https://web.archive.org/web/20111007025556/http://www.latrobe.edu.au/phimvt/joy/j02maf.html https://web.archive.org/web/20111007025556/http://www.latrob... [2] http://factorcode.org/ http://factorcode.org/ [3] http://kittenlang.org/ http://kittenlang.org/
- hashmal 9y agoIndeed, concatenative languages are a perfect example of point-free notation. I don't see the point (pun!) of doing it in JS or any mainstream language though. They are also a joy to design/implement* and doing it really changes how you think about programming. * http://hashmal.github.io/shirka/ http://hashmal.github.io/shirka/ my personnal attempt at designing such a language. warning: very old, dirty, buggy code.
- mrkgnao 9y agoTasteful, minimal pointfree-ization can sometimes help one think at the "level" of functions instead of thinking about how those functions transform arguments. If you're familiar with the Maybe type (called Option in Rust and Java, and perhaps Swift?), this example might help. For concreteness, this is how it's defined in Haskell: data Maybe a = Nothing | Just a Consider this function, called `maybe` in Haskell: maybe :: t -> (a -> t) -> Maybe a -> t maybe defaultVal _ Nothing = defaultVal maybe _ f (Just v) = f v This unwraps a Maybe value, transforming the value inside if it exists and taking a default to return if it doesn't. So, for example > maybe 4.0 sqrt (Just 9.0) 3.0 > maybe 4.0 sqrt Nothing 4.0 Now consider this function: withMinDefault :: Maybe Int -> Int withMinDefault = maybe intMin (* 2) Here (* 2) is a partially-applied function that doubles a number: ((* 2) 3) = 6. The advantage of defining `withMinDefault` like this instead of the equivalent "pointful" version withMinDefault' :: Maybe Int -> Int withMinDefault' n = maybe intMin (* 2) n is minimal at best, but the idea is that we're defining this function in terms of `maybe` directly: "withMinDefault multiplies an optional number by 2, defaulting to INT_MIN if it's not present." You can combine this with function composition to start working by almost exclusively manipulating functions, and (when done tastefully, as I stated at the beginning) it can greatly improve the clarity of the code for an experienced programmer who can read streams of functions with facility.
- KirinDave 9y agoLet me provide some other examples then. Often the most relatable examples in this field involve manipulating data structures, or hiding abstractions. Sometimes you just want to reuse an existing function with 2 arguments. You name it either because you want to give it an explicit type (in many cases the exact same code can do different things depending on the type it expects). People's first exposure to point free style is often "flip", a higher-order function that takes a diadic function and swaps the order of arguments. As an example, the "cons" function from data.text takes a character and a text object and prepends the character into the text object. It has type: Char -> Text -> Text But what if we'd like to do this repeatedly? Ideally we'd reuse a function like foldl (reduce in javascript parlance). What we'd like is to have a function like conss that has type Text -> [Char] -> Text. Sadly, foldl expects its reducer function to take something more like Text -> Char -> Text, which is really close to what we already have with cons. A point free solution feels pretty natural here: conss :: Text -> [Char] -> -> Text conss = foldl (flip cons) We might say, "I actually want to prepend the same prefix to hundreds of strings." A simple point free way to think about reusing our old code: prepend :: [Char] -> Text -> Text prepend = flip conss Often times we use point-free style when we're building other structures. Some time ago I wrote a text adventure game engine to explain Haskell to some kids, and it has this line (https://github.com/KirinDave/Rag/blob/master/src/Rag/Parser.hs#L21-L22 https://github.com/KirinDave/Rag/blob/master/src/Rag/Parser....): buildMaze :: [(Int, Room)] -> MazeDefinition buildMaze = foldr (uncurry Map.insert) Map.empty Again, a point free version of this is much more readable and direct than if I were to pattern match into the arguments. That would require a binding both on the outside for the array and in the inner reducer function, adding tons of noise: buildMaze :: [(Int, Room)] -> MazeDefinition buildMaze definitions = foldr reducer Map.empty definitions where reducer = \(m, (i,r)) -> Map.insert i r m Sometimes, point-free style can also help with very abstract code. A common way to define the functor instance for the Free monad, for example, is reasonable to follow in point free notation but pretty brutal without it: instance Functor f => Monad (Free f) where return = pure Pure a >>= f = f a Free x >>= f = Free ((>>= f) <$> x) Trust me, you don't want to see the lambda-ized version of that inner bind. It's really, really bad. It also loses the clarity this one offers about the fact that at the end of the day, you make a big chain of functions and then fmap it (which is actually pointing to a massive performance flaw with this implementation free monad!). I agree that the author didn't really make their point here. What's more, using the language they did will always make point free notation look awkward. Point free notation thrives in environments where partial application is automatic.