8 ms·
The thing with functional languages is that most imperative languages can "do functional things", but not vice versa. For example, many algorithms are beautifu
by dispose13432 10y ago
The thing with functional languages is that most imperative languages can "do functional things", but not vice versa.
For example, many algorithms are beautiful when expressed in a recursive manner (Fibonacci, for example) , and pretty much all languages permit one to do recursion (even old fashioned C).
But many algorithms are much more elegant when expressed through for loops. For example, if one needs to iterate through lines keeping state into account, a
for (int i =0;i<len;i++){
if(lines[i] == "a"){
i+=2;
}
}
seems much cleaner than an equivalently functional algorithm.
-----
EDIT.
I (purposefully) left out the "main" logic (as it's not really relevant to the post).
Of course in a "real" example, the code would look like:
for (int i =0;i<len;i++){
if(lines[i] == "a"){
i+=2;
} else{
parseLine(lines[i]);
}
}
The goal is to skip the line after a line starting with the letter "a".
- knocte 10y agoCleaner, but there's mutation involved, which could bring side effects.
- dispose13432 10y agoMutation could cause bugs, but so can logic mistakes cause bugs.
- adjkant 10y agodeleted, misread code
- deleted 10y ago[deleted]
- gravypod 10y agoOr sum(filter (lambda line: line == "a", lines)) * 2
- gravypod 10y agoI misread the comment. I'm incorrect here.
- aduffy 10y agoI could imagine the above being expressed very concisely and elegantly in a language like Haskell: f = sum $ map (\x -> if x == 'a' then 2 else 0) I tend to think that as the accumulation condition gets more complicated, the for loop begins to be the clearer choice, but of course there's always a way to simplify into a number of map steps. This can lead to a bunch of allocation overhead for the intermediate lists unless iterators are used in languages where those are supported. EDIT: Whoops, read and replied on mobile, missed the parseLine() call :/ EDIT EDIT: Looks like my code matched the parent as of when I read it, before the edit. wyager is correct that I messed up my point-free syntax
- lgas 10y agoYour function doesn't do the same thing his does -- his skips comparing some of the lines because he's selectively incrementing the loop counter.
- aduffy 10y agoI wrote the comment before the parent made their edit so the logic did match what it was at the time. Having said that it would've been a very simple extension to make it a recursive algorithm which includes the else case, and arguably would be clearer than the for loop solution.
- sgk284 10y agoI think that's a perfect example of why a functional implementation is superior here. The index is being mutated in two different spots (i++ and i += 2) and it's easy to miss the fact that the i += 2 isn't just a running sum but actually impacting the future traversal. In Haskell you'd do something like: skip [] = [] skip ("a":xs) = skip (drop 2 xs) skip (x:xs) = x : (skip xs) Which makes it super clear that you're skipping over items in the list. The parsing then simply becomes: map parseLine (skip lines) Which is at least an order of magnitude clearer and doesn't mix a bunch of unrelated logic together. Even worse, the original author says the goal of his code is to: > The goal is to skip the line after a line starting with the letter "a". But he actually skips two lines after an "a". The above Haskell code makes it clear that we're skipping two lines with "drop 2".
- kahrkunne 10y agoNot all functional languages are purely functional. (Common) Lisp can be described as a functional language, but it can do loops like those just fine. A lot of imperative languages don't even have map or reduce; some don't even have proper first-class functions. The same argument goes the other way too.
- johncolanduoni 10y agoMost functional languages cannot do imperative things as smoothly as imperative languages, but there are very few functional languages that can't do them at all. OCaml, Haskell, and their derivatives can all do precisely what you listed above just fine. It's just a tradeoff to make one kind of programming easier at the expense of another (through perhaps not a necessary tradeoff).
- wyager 10y ago> Most functional languages cannot do imperative things as smoothly as imperative languages I don't even buy this for most definitions of "imperative things". The only algorithms that I've ever found unwieldy in functional programming are heavily array-oriented algorithms like random shuffles. Almost everything else comes out more elegant.
- johncolanduoni 10y agoWhen I say "imperative things", I mean insisting on doing things in an imperative manner (for loops, mutation, etc.), not the problems themselves.
- wyager 10y agoWhen transliterating code from distributed systems papers (uncomfortably imperative) to Haskell, I had an excellent experience with Lenses and the State monad. It allows you to reproduce imperative, mutable code in functional Haskell and without mutability. The semantics are also much more restricted (in a good way) than imperative code in a real imperative language. For example, you have to specify the entire state beforehand, which prevents you from falling for ultimately unproductive temptations like global variables. You can even do for loops if you want. http://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Traversable.html#v:for http://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Tr... for [1,2,3] $ \i -> do counter += i print i I wrote a bit about the experience here if you're interested. It was on HN a while back. http://yager.io/Distributed/Distributed.html http://yager.io/Distributed/Distributed.html
- 10y ago
- deleted 10y ago[deleted]
- johncolanduoni 10y agoWhat does this algorithm do? As far as I can tell there's only an effect if len is wrong, or the list has length 2^31-1 and ends with a. Even if you move i out of the for loop, you would just get whether the list ends in "a". Edit: Whoops, 2^31-2 is sufficient for the overflow and i's value at the end of the loop is more subtle than I gave it credit for.
- platz 10y ago> iterate through lines keeping state into account You mean like a fold, the bread and butter of FP data processing?
- wyager 10y ago> for (int i =0;i<len;i++){ if(lines[i] == "a"){ i+=2; } } I think you made a typo. This doesn't do anything. If that's the case, a functional formulation would have actually made it quite obvious. But here's a way to do it in Haskell assuming you didn't make a typo. f i xs | i >= len xs = i | xs ! i == 'a' = f (i+2) xs | otherwise = f (i+1) xs There is almost certainly a more elegant way to write your algorithm if I understood what you were trying to do, but here's sort of a "worst case" direct translation. EDIT: In response to your edit, a substantially more elegant functional approach looks like map parse . filter (not . startsWith "a") I think you ironically chose a task that functional programming is particularly better for.
- hskl 10y ago> map parse . filter (not . startsWith "a") Almost! You have to skip two lines.
- wyager 10y agoTrue. OK, let's start with the OP's goal, not his code (which appears to have been consistently incorrect, conveniently and humorously contradicting his intended point). > The goal is to skip the line after a line starting with the letter "a". OK, so we want the following: ["x", "y", "a", "skip", "z"] -> ["x", "y", "a", "z"] Here's a non-clever but still more elegant (and correct) way: f (x:y:zs) | ('a':_) <- x = x : f zs | otherwise = x : f (y : zs) f zs = zs I guarantee you that there are multiple zippy one-liners that do the same thing, but I'll leave it at that for now. It's already a huge improvement, and does what it purports to!
- uryga 10y ago('a':_) <- x = x : f zs What's the `<- x`for? I know some Haskell and I've never seen that syntax. And `('a':_) = x : f zs` should be enough I think. Also I'm pretty sure each line is supposed to be a string, so it should be `("a":_) = x : f zs` :)
- 10y ago
- DigitalJack 10y agoThis is a good example of the result of being inculcated into the von Neumann machine method of computing (small chunks at a time vs operating on a whole). It's very familiar to most of us and it is not easy to find new points of view.
- hskl 10y ago> for (int i =0;i<len;i++){ if(lines[i] == "a"){ i+=2; } else{ parseLine(lines[i]); } } You must be one of those managers who like to dabble in coding! I don't even know what you wanted to do. Is that C++? That part `lines[i] == "a"` is string comparison, right? And `len` is the amount of lines, right? I'll use a list instead. I'd do it in C# with LINQ but I'm at my folks, so I only have an iPad. Here's the biggest Haskell program I ever wrote in my entire career: dropTwoIf :: (Eq a) => a -> [a] -> [a] dropTwoIf _ [] = [] dropTwoIf s (x:xs) | s == x = dropTwoIf s (drop 1 xs) | otherwise = x : dropTwoIf s xs myList = [ "will pass", "will pass", "a", "will skip", "will pass", "a", "will skip", "a" ] main = mapM_ print (dropTwoIf "a" myList) Result: "will pass" "will pass" "will pass" Thanks for forcing me to actually do something bigger than Hello World of fib in this Haskell thing. EDIT: Sure, it looks bigger than the for loop, but at least it has a name (but I'm not that good at naming) and it can be put in a library and reused. I'm pretty sure I could do MUCH better if I actually knew the standard library. EDIT 2: Huh, figured out the type.
- dispose13432 10y ago>You must be one of those managers who like to dabble in coding! I don't even know what you wanted to do. Is that C++? That part `lines[i] == "a"` is string comparison, right? And `len` is the amount of lines, right? I'll use a list instead. It was more along the lines of pseudo-code (I couldn't be bothered to type the whole srcpy business or to look up C++ array length method).
- malisper 10y agoWhile I'm not a huge fan of functional programming, this can be done pretty easily with a fold. For example in Scala: xs.foldLeft(""){ (prev, cur) => if (prev != "a" && cur != "a") parseLine(cur); cur} Although the result is pretty and shorter than your solution, it took me quite a while to figure out how to do it. Also, your code has a bug. You are skipping two lines after every "a". The code should be i += 1 instead of i += 2. <Insert lame joke about mutation causing bugs here.>
- hskl 10y agoHere's your version translated to C#: xs.Aggregate("", (prev, cur) => { if (prev != "a" && cur != "a") parseLine(cur); return cur; }); Working demo: https://repl.it/EwaE https://repl.it/EwaE
- dispose13432 10y ago>Also, your code has a bug Oops. You're right. Got spoiled by foreach loops lately :)
- gizmo686 10y ago>for (int i =0;i<len;i++){ if(lines[i] == "a"){ i+=2; } } This algorithm can, without modification, be done in a functional language. For example, consider the following Haskell: import Control.Monad.Loops import Control.Monad for_ :: (Monad m) => m () -> m Bool -> m () -> m () for_ init guard step body = init >> whileM_ guard (body >> step) loop :: [string] -> State Int () loop lines = for_ (put 0) (get (<(length lines))) (modify (+1)) (when (get >>= \i -> (lines!!i == "a")) (modify (+2))) I did not use do notation to avoid accusations of cheating; and am defining for_ here because Haskell's standard library for loop only iterates through data-structures (like the for loop of many imperative languages). I will also admit that the Haskell version is ugly due to its verbosity (because, even though it is possible, it is not how you are supposed to do things in Haskell). EDIT: Made for_ generic and added type signature for loop. If you want multiple "variables" in this approach, you can define a datatype to store them: data S = S { i :: Int ... } In this case, we can access i by replacing "get" with "gets i" in the above example ( "i" being an accessor function that Haskell automatically defines when we create a datatype with a field called "i"). You can imagine the generated value "i" being a more complicated datatype that can be both a getter and setter, in which case we can define "puts i" to be the analog of "gets i" and "put". I think I have seen this approach done in Haskell but cannot remember what it is called.
- dvfjsdhgfv 10y ago> This algorithm can, without modification, be done in a functional language. For example, consider the following Haskell: The OP clearly stated the iterative version is cleaner, and you seem to confirm his pint.
- mathetic 10y agoExcept it is not cleaner. It is just more familiar to you because most languages in use in the past 40 years have been influenced by Algol family of languages for their syntax. Now the author is being modest here: First, `for` is defined out of simpler function `while`. You can do the same for the for loop in C of course, but only as a function call and to pass arbitrary body you would need to use a function pointer, which I argue is overwhelmingly more confusing. You may have different opinions merits of of baking non-orthagonal features into the compiler, but surely you'll appreciate the clarity here. Second, `do` notation is not used which would make it look more like Algol family of languages with semicolons to delimit (if "statements" (really, monadic expressions) are on the same line otherwise line feed is enough.) and less `>>=` would appear. Third, giving type annotations which can always be inferred unless you are using advanced extensions such as Generalised Algebraic Data Types (GADTs), which are not used here. Also less characters do not mean cleaner. For example the `loop`'s type annotation produces a `State` monad with `Int` as the state. This means that `loop` will not be connecting the Internet, it will not be throwing an exception [0], etc. Even in the narrow effect of manipulating state it guarantees it will only simulate mutation of a single variable of type `Int`. All of that guaranteed from a single line is a remarkable manifestation of clarity. [0] I don't account for undefined and the problem of strictness, that's a discussion for another day.
- spion 10y agoI find it more interesting that this program has a bug even though there are just two pieces of code, not more than 10 characters apart that modify only one shared mutable integer.
- tigershark 10y agodef parsedLines = lines.withIndex().filter{l, i -> i < 1 || lines[i - 1] != 'a'}.collect{parseLine(it)} Do you really think that the imperative version is simpler that this small groovy one liner? I strongly disagree. And probably in a more functional language than groovy you can do even better. And btw you are not doing anything with the result of parseLine, while I am saving it in a new collection of parsedLines.
- vorg 10y ago> def parsedLines = lines.withIndex().filter{l, i -> i < 1 || lines[i - 1] != 'a'}.collect{parseLine(it)} In that Apache Groovy sample, you put an `l` as the first parameter to the `filter` but don't use it anywhere, which is not simple for people reading it. The logic instead requires you to refer to the global input `lines`, which is better expressed imperatively. And btw in Groovy that code would probably only work on ASCII data.
- tigershark 10y agoReally is it so difficult to ignore one parameter for people reading that one liner? I could have used the underscore but honestly when I'm writing from the phone I don't usually care for small things that don't change absolutely nothing. At least my code doesn't have the bug that skips TWO lines after the a, like the original code, and it actually saves the parsed lines in a new collection rather than throwing them away and doing nothing at all apart from burning cpu. But for some strange reason you still prefer that obscure bug-ridden and useless imperative code rather than the self explaining and declarative functional version. Enjoy your imperative bugs :)
- vorg 10y agoAny code that's not tested has bugs. As for reading code, when I read yours I can't work out why you put .filter{l, i -> i < 1 || lines[i - 1] != 'a'} instead of .filter{l, i -> i < 1 || lines[i - 1][0] != 'a'} to skip the line after one that begins with an 'a', or .filter{l, i -> i < 1 || lines[i - 1] != "a"} to skip the line after one that is an "a". I'd need to go run it to work out what it does.
- ecthiender 10y agoI'm a Haskell beginner. But this is my attempt at solving your algo functionally in Haskell. Note there's no shared state. fn ("a":lines') = fn (skip2 lines') fn (l:lines') = (parseLine l) : fn lines' skip2 = tail . tail Gave spaces above for readability. The function "fn" pattern matches "a", then skips 2 elements of the array and recursively calls itself. Haskell programmers can correct if the program is correct/sound. I just wrote this from my mind. Not near any computer now.
- ecthiender 10y agoOops forgot to add the base condition to the recursion: fn [] = []