13 ms·
Destroy All Ifs – A Perspective from Functional Programming
- dozzie 10y agoThe inversion of control flow from called to calling function is an interesting way to describe (part of) functional programming style. I hadn't thought of that this way, even though I use it for quite some time.
- throwaway13337 10y agoThis just seems to obscure the logic. Not unlike how polymorphism can make code flow harder to read, though feel more clever. There is a place for it - like when you're trying to express a set of logic that will be guarded by the same condition, but always at the cost of some complexity. A set of conditionals is probably the most obvious way to express branching.
- dwb 10y agoTry it before you knock it. I might have said something similar before getting into Haskell, but now I'm nodding along happily. Dealing in meaningful data types with small composable functions is very pleasant for me now.
- TeMPOraL 10y ago> Try it before you knock it. That's what I recommend too[0] - with the added caveat that you shouldn't be afraid to "knock it" if it turns out to be honestly bad. Sometimes the idea turns out bad, sometimes it turns out great - but you'll never know it if you don't try; just be honest with yourself during that trial. [0] - https://news.ycombinator.com/item?id=12108138 https://news.ycombinator.com/item?id=12108138
- dwb 10y agoAbsolutely, but I'd also add that what works in one language/environment may not work in another. I wouldn't be surprised if writing Haskell-style code in Python didn't work out that well, for example!
- TeMPOraL 10y agoI agree. I used to earn my bread in PHP and now I do it in Java, while after hours I mostly code in Common Lisp. I have some experience trying to port idioms from the latter one to the former two. Sometimes it turns into a good idea (God, how much my life in one PHP project was simplified by porting over #'mapcan), sometimes you quickly realize it's stupid and makes zero sense. But I think that's again a case of taste acquired by experimentation :). A big enemy here is simply sunk costs fallacy - no matter how emotionally invested you're in some idiom, sometimes it really doesn't fit the other language.
- eru 10y agoI have tried. Two things get in the way quickly, and that's even just expressing thing, not even looking at performance yet: * Python standard library functions, especially the ones on dicts, mutate and don't return the new dictionary. * Python's syntax for creating functions is awkward: lambdas are cumbersome, and so are the operator package and eg functools.partial; there's no really convenient way to compose functions.
- andrewaylett 10y agoYour first point is actually something I really like about Python's API design: in general, methods operating on collections either mutate the collection /or/ they return it. So it's clear at the point of use whether you're dealing with the same object or a new one. This is something that bugs me about the fluent builder pattern in Java -- continuing to return `this` until suddenly you don't any more, and you can't re-use 'intermediate' values because they're actually all the same object.
- eru 10y agoSure. I'd just like to have a nice set of operations to manipulate dicts that don't mutate and return the result, too.
- jakub_h 10y ago> A set of conditionals is probably the most obvious way to express branching. Besides object polymorphism and sets of conditionals, there's also generalized predicate dispatch, but that's probably an overkill for many things.
- barrkel 10y agoDefine true as a lambda taking two lazy values that returns the first, and false as one that returns the second, and you can turn all booleans into lambdas with no increase in code clarity. The straw man in the post - talking about a case-sensitive matcher that selectively called one of two different functions based on a boolean - is indeed trivially converted into calling a single function passed as an argument, but it's hard to say that it's an improvement. Now the knowledge of how the comparison is done is inlined at every call point, and if you want to change the mechanism of comparison (perhaps introduce locale sensitive comparison), you need to change a lot more code. That's one of the downsides of over-abstraction and over-generalization: instead of a tool, a library gives you a box of kit components and you have to assemble the tool yourself. Sure, it might be more flexible, but sometimes you want just the tool, without needing to understand how it's put together. And a good tool for a single purpose is usually surprisingly better than a multi-tool gizmo. If you have a lot of need for different tools that have similar substructure, then compromises make more sense. This is just another case of the tradeoff between abstraction and concreteness, and as usual, context, taste and the experience of the maintainers (i.e. go with what other people are most likely to be familiar with) matters more than any absolute dictum.
- frozenport 10y ago>> you want to change the mechanism of comparison (perhaps introduce locale sensitive comparison) I couldn't agree more, and this is why I think most FP programs are about as intellectual stimulating as `std::min_element`
- svanderbleek 10y agoHow does using if statements make it easier to introduce locale sensitive computation? A locale should be represented in the arguments or as a transformation, very similar to what the article is doing.
- dwb 10y agoA library can very easily provide, along with the kit components, convenience functions that perform common tasks - like matchCaseInsensitive or whatever. The point I took from the post is that, regardless of how the final public API is presented (and indeed, hopefully it doesn't involve piecing together umpteen bits), the code implementing it can be written by composing simple components rather than unwieldy conditionals.
- Animats 10y agoThe idea that each type has its own control flow primitives is bothersome. It's taken over Rust: argv.nth(1) .ok_or("Please give at least one argument".to_owned()) .and_then(|arg| arg.parse::<i32>().map_err(|err| err.to_string())) .map(|n| 2 * n) I'm waiting for date.if_weekday(|arg| ...) Reading this kind of thing is hard. All those subexpressions are nameless, and usually comment-less. This isn't pure functional programming, either; those expressions can have side effects.
- koenigdavidmj 10y agoMost of this should be obviated when the ? operator is ready. But until then, there is no primitive for 'work on the type you wrapped in Option, short-circuiting and returning None at the first sign of failure', so it has to be done in the library.
- Animats 10y agoIf this is an accepted idiom, people will be using it for years to come. Sometimes only to be cool. With "?" and "try!()", Rust is sort of emulating exceptions in a weird way.
- TeMPOraL 10y agoI started using a similar idiom in Java 8 recently, inspired by Optional class and some new functions in Map interface. It took me an hour or three, but at some point I noticed that in few places I was writing like this just because it's "cool", even though it was less readable (and actually made it harder to use one code-navigation IDE extension I liked). A lesson this strengthens in me again is that sometimes a cool-looking idea turns out to be pretty bad in practice, but you only figure it out after you go ahead with it. It isn't bad to try out things (that's how we learn), but you need to be extra honest with yourself about how the thing really feels when you're first using it, and never ignore that sense this new idea actually doesn't fit well and should be rejected.
- steveklabnik 10y ago
- whazor 10y agoReducing if statements does shrink the possible state space, however using additional abstraction might increase it even further.
- lilbobbytables 10y agoOften times when I read about "ideal" ways of programming, I'm curious if it's ever implemented in a production code base built by a team.
- danbolt 10y agoI'd love to see what ways of programming tend to be most effective with production teams. My gut thinks the solutions will be a little more boring than our inner magpies will want to admit.
- jlg23 10y ago> I'd love to see what ways of programming tend to be most effective with production teams. The one the whole team understands and can agree upon.
- moron4hire 10y agoUsually in teams of younger engineers on the first project they have control over themselves.
- runeks 10y agoMe too. Particularly because every programmer has their own idea of what a "right"/ideal style of programming is. Here, apparently, we must not use conditionals. The more I write code the more I realize that the entire purpose of the code is to have some effect on reality, and the more reliably it can do this, the better the code. I find I code a lot better without design principles, because trying to remember which patterns are "good" and "bad" just obscures the attention I would have used to look at the code and sense whether something would work in this particular situation.
- userbinator 10y agoThe more I write code the more I realize that the entire purpose of the code is to have some effect on reality, and the more reliably it can do this, the better the code. Very nicely put. The only "principles" I keep in mind when I write code are simplicity, correctness, and efficiency, and those tend to all be correlated.
- iopq 10y agoThis was the idea I used when I did https://bitbucket.org/iopq/fizzbuzz-in-rust/overview https://bitbucket.org/iopq/fizzbuzz-in-rust/overview I still had to bottom out at https://bitbucket.org/iopq/fizzbuzz-in-rust/src/9e5fcaabbd5f364839be929a974db0ac31d79c2e/src/lib.rs?at=master&fileviewer=file-view-default#lib.rs-53:56 https://bitbucket.org/iopq/fizzbuzz-in-rust/src/9e5fcaabbd5f...
- Eliezer 10y agoI thought the argument was going to be "Conditionals are bad for running on GPUs."
- dahart 10y agoI read everything I could find on the Anti-IF site and didn't understand what the mission is exactly. They qualify and mention they want to remove the bad and dangerous IFs, but I couldn't find examples that differentiate between bad ones and good ones -- are there good ones according to this campaign? I like using functional as much as anyone, and removing branching often does make the code clearer and remove the potential for mistakes. But I admit I have a hard time with suggesting people prefer a lambda to an IF, or to not ever use an IF. A lambda is, both complexity wise, and performance wise, much heavier than an IF. And isn't is just as bad to abstract conditionals before any abstractions are actually called for?
- CamperBob2 10y agoI read everything I could find on the Anti-IF site and didn't understand what the mission is exactly. I have a similar problem, in that every time I try to understand the perspective of functional-programming advocates, I find that the authors always seem to illustrate their points with examples like this: match :: String -> Boolean -> Boolean -> String -> Bool match pattern ignoreCase globalMatch target = ... If I'm already literate in Haskell or Clojure or Brainfuck or whatever godawful language that is, then chances are, I'm already familiar with the strengths of the functional approach, and I'm consequently not part of the audience that the author is supposedly trying to reach. So: are there any good pages or articles that argue for for functional programming where the examples can be followed by a traditional C/C++ programmer, or by someone who otherwise hasn't already drunk the functional Kool-Aid?
- GregBuchholz 10y agobool match(char *pattern, bool ignoreCase, bool globalMatch, char *target) { ...
- l_dopa 10y agoThe problem's not on your end -- a lot of these blogs are just junk, probably the vast majority of ones that fall under "advocacy". As far as I can tell, the author's objection to conditionals is based on a misunderstanding of a different blog post[0]. It's nonsense. Really understanding where FP is coming from requires an introduction to programming language semantics[1]. Interesting stuff, but not immediately useful to a working C programmer. [0] https://existentialtype.wordpress.com/2011/03/15/boolean-blindness/ https://existentialtype.wordpress.com/2011/03/15/boolean-bli... [1] http://www.cs.cmu.edu/~rwh/pfpl.html http://www.cs.cmu.edu/~rwh/pfpl.html
- dwrensha 10y agoI recommend Bob Harper's essay on "boolean blindness": https://existentialtype.wordpress.com/2011/03/15/boolean-blindness/ https://existentialtype.wordpress.com/2011/03/15/boolean-bli... An excerpt: > The problem is computing the bit in the first place. Having done so, you have blinded yourself by reducing the information you have at hand to a bit, and then trying to recover that information later by remembering the provenance of that bit.
- AstroJetson 10y agoThats why you use Lua, it lets you have multiple return values. So you can get a boolean back to let you know if the strings were the same, an int to know where they ceased matching and a boolean to let you know if they are case different. It's then up to the programmer to decide how much enlightenment they want. The destroy all IF reminds me of GOTO considered harmful of the 70's. There are other ways to fix the problem.
- TeMPOraL 10y agoMultiple return values are a great idea IMO, and I learned to really appreciate them in Common Lisp. They're best used as additional bits of information that the programmer may or may not find useful. Like that string comparison example of yours. Or #'gethash[0], that will return the value from hash table you're looking for or a default value (which is optional and by default NIL) if there is no entry with a given key, but it'll also return a second, boolean value that tells you whether your return value was actually found or not - which cleanly solves the problem of storing NILs in hash tables, in the places you care about it. Multiple return values feel elegant also because the compiler can optimize them away when you're not using them, which is the most common case. [0] - http://clhs.lisp.se/Body/f_gethas.htm http://clhs.lisp.se/Body/f_gethas.htm
- tamana 10y agoThe Lua solution is clutter. Why return a boolean just to switch on it and then discard it?
- 10y ago
- white-flame 10y agoThis whole campaigned is misguided. "Bad IFs" are a code smell, and they're being scapegoated when the real problems are management demanding that simple hackish prototypes & tests be deployed into production, management that doesn't allow time for refactoring, and poor programmers who think that "bad IFs" are good code. But the main site also doesn't do any reasonable job of defining what a "Bad IF" even is. The crux of the matter is that programmers need time to craft the details of a project to avoid or correct technical debt. These sort of reactions just point out one tiny portion of technical debt itself and doesn't solve any fundamental problems at all. (and yeah, I known I'm ranting against the Anti-IF campaign, not the particular take on the linked site. But this article just seems to parameterize the exact same parameters that are branched on anyway.)
- lifeisstillgood 10y agoI think that aiming at the management of the coders and the business users is putting the emphasis in exactly the right place. Once we get to womdering if eliminating IF stmts will help, we have passed by so many opportunities for 10x value delivery.
- bunderbunder 10y agoThe "technical debt" metaphor gets so much better if you take the analogy more literally than most people do. Like for financial debt, the optimal amount is not necessarily zero. Oftentimes taking on or carrying debt allows you to generate more profit than you could by avoiding it or paying it down. That said, most places I've worked manage it poorly. Few people really understand that, just like financial debt, it's something that needs to be taken on and managed in a mindful and deliberate manner.
- eru 10y agoAlso, if you do take the finance metaphor, going into debt is not good by itself. It's the investments you make with that debt that are good, and potentially outweigh the burden of debt. (And can be cheaper than equity financing.) Going back to programming: debt-fuelled programming should buy you something, eg speed to market, and is not a good in itself.
- true_religion 10y agoThis is the starter code: publish :: Bool -> IO () publish isDryRun = if isDryRun then do _ <- unsafePreparePackage dryRunOptions putStrLn "Dry run completed, no errors." else do pkg <- unsafePreparePackage defaultPublishOptions putStrLn (A.encode pkg) This would be nicer if you could do multiple functions with pattern matching. In Elixir this would be: @spec publish(boolean) :: any def publish(true = _isDryRun) do _ = unsafePreparePackage dryRunOptions IO.puts "Dry run completed, no errors." end def publish(false = _isDryRun) do pkg = unsafePreparePackage defaultPublishOptions IO.puts (A.encode pkg) end Pattern matching is pretty powerful, even going as far to give a dynamic, non-statically types language like Elixir the ability to 'destroy all iffs' too.
- dwb 10y agoYou can do exactly the same kind of pattern matching in Haskell, but that's not at all the point of the article. It's equivalent to writing the conditional, it doesn't remove it. publish :: Bool -> IO () publish True = unsafePreparePackage dryRunOptions >> putStrLn "Dry run completed, no errors." publish False = do pkg <- unsafePreparePackage defaultPublishOptions putStrLn (A.encode pkg)
- twblalock 10y agoPattern matching is just as explicit as an if loop. In languages that implement it for null values, it is just as explicit as typing "if (foo == null)" in an imperative language. You have to think about it, and type just as much code to deal with it, as you would in a language without pattern matching. The only upside to pattern matching that I can see is that you are forced by the compiler to match all possible inputs and check for nulls in some languages, which can help you avoid null pointer exceptions and such. But you haven't encapsulated anything, or saved yourself any thinking or typing, by using pattern matching. You've basically turned every function into a switch statement. It's vastly overrated.
- timmytokyo 10y agoAnother advantage of pattern matching is extensibility. Suppose you wish to add a new branch case. Under the traditional if/else (or switch) model, you'd need to modify the function containing the if statements. With pattern matching, you simply introduce a new function; it decentralizes the change and acts as a sort of simple, intuitive polymorphism.
- asQuirreL 10y agoThe article seems to advocate type synonyms like the following: type Case = String -> String -- ... type Announcer = String -> IO String I would argue that these are actually much worse than not having type synonyms at all. (String -> String) functions could do anything to your query parameter and text, the type is too coarse, and the inhabitants too opaque for us to reason about them easily. Naming the type suggests the problem is solved without actually having solved it. It is like finding a hole in the ground, and covering it with leaves, so you don't have to look at it anymore. You are literally making a trap for the next person to come this way. In an ideal world you would be able to use refinements to say that you want any (f :: String -> String) such that `toUpper . f = toUpper` but without such facilities, I think I may just settle for: newtype Case = CaseSensitive Bool Sometimes, your type really does only have two inhabitants.
- joeyh 10y agodata Case = CaseSensitive | CaseInsensative This is just as efficient as the newtype, and leads to clearer code when matching on the value. Also, sometimes types you thought only had two inhabitants get a third one added later, which this facilitates.
- asQuirreL 10y agoClarity is a bit subjective, I think. The difference between: CaseSensitive CaseInsensitive Is harder to spot (for me) than between: CaseSensitive True CaseSensitive False This is because the bit that is the same is all on one side, and the bit that is difference is all on the other side. Case in point, your data definition has a typo: `CaseInsensative`, which occurs after the `In` shifts it away from the bit it should be the same as in `CaseSensitive`. Every little bit helps. What's more, while you may be right that at the surface, the two representations are equally performant, what the newtype has that the data declaration does not, is the Prelude's definitions of all the boolean operators. If you wish to perform any more complicated logic with your data declarations treating them as booleans, you must either cast them to booleans (which comes at a runtime cost), or you must replicate the functionality of the Prelude for your custom type (which comes at a development cost). Your branching logic (which, let us suspend disbelief and say is "not so bad", just for now) may require the combination of multiple such booleans, which in your encoding scheme would each get a different type due to their semantics, then we can't even viably define our custom boolean operators, so are forced to cast everything to booleans. The point I'm making here is that outwardly, you want the type to reflect the semantics of how its values are used, but inwardly, you want access to its representation in a way that makes it easy to combine (or put another way, depending on who's looking, the semantics of a value changes). Also, there is nothing stopping you from changing code later to meet changing needs. Using a newtype now doesn't preclude you from ever using a data declaration in the future. Certainly, you will have to change the patterns and constructors used in a couple of places, but that is a matter of minutes: Time you have already spent weighing the future implications of this decision in your mind right now, so this sensation of time saved is a fallacy.
- VladKovac 10y agoFunctional programmers love to emphasize how all the aspects of programming that their pet language is uniquely good at dealing with also happen to be the biggest problems in code maintenance. Is there any actual data on what the biggest problem sources are?
- swift 10y agoI'd love more data on this too, but I do think it's worth pointing out that it's pretty uncontroversial that the more control flow paths you have, the harder your code is to reason about. That's the basic assumption of the notion of cyclomatic complexity, after all.
- cdevs 10y agoBad programmers will mess any syntax restrictions/guidelines/styles we put on them. If you let them make any function were they can put launchNukes(); into doX(); then they will. though running things as a service may be the future, this launchNukes(); function is over here....safe from you.
- qwertyuiop924 10y agoThis is, as many commenters have noted, just another overzealous programming doctrine. Just like 'GOTO considered harmful.' Here's the deal: if is a flow control primitive. Just like goto and while. If (heh) that primitive isn't high-level enough to handle the problem you are facing, it is incumbent upon you as a programmer to use another, higher level construct. That construct may be pattern matching, it may be polymorphism (or any other form of type-based dynamic dispatch). It may be a function that wraps a complex chain of repeated logic, and is handed lambdas to execute based upon the result. It may, as in the article given here, be a funtion that is handed lambdas which apply or do not apply the transformation described. The point is, there are many branch constructs, or features that can be used as branch constructs, in most modern programming languages. Use the one that fits your situation. And if that situation isn't a that complex, that construct may be if. Fizzbuzz using guards is the most clean and modifiable fizzbuzz that I've seen in Haskell. Although now that I think about it, if you provide a function with a list of numbers...
- eru 10y agoNot all control-flow primitives are necessary. Eg Haskell and Scheme get by without 'while' and 'goto'. Haskell would do just fine without a built-in 'if': you can define 'if' as a function via pattern matching. Given that perspective, the article would be a call to use more expressive types than Booleans to match on---and in lots of cases not to match at all, but provide what would be the result of the match as an argument to the function.
- qwertyuiop924 10y agoScheme and Haskell have other primitives that take the place of while and goto. But yes, using more expressive match types or parameters is a good idea. As for providing the result as an argument, that can be a good pattern, but isn't always practical. Note what I said in my original comment about using your own discretion.
- eru 10y agoThey don't have `other primitives': they have function calls. Most languages have function calls these days.
- astazangasta 10y agoWhy don't we just treat this like writing? Good writing has one clear imperative: communicate meaningfully the intent of the author to the reader. Good code is no different; it is merely expressive writing in a different language, with, perhaps, greater constraint on its intent. Some people make up rules like "don't use adverbs", or "don't split infinitives", in an effort to write better. But this doesn't necessarily produce good writing; sometimes an adverb is just what you need. The same is true of code. These are useful things to think about, but "destroy all ifs" is akin to "never use a conjunction".
- swift 10y agoI get what you're saying, but that's definitely not what good writing means in the context of, say, poetry, or literary fiction. Programming is best compared to technical writing or cookbooks, I think. I realize this is one of those irritating "actually," replies, but what can I say, I'm sensitive about this topic. =)
- AYBABTME 10y agoThe author seems to ignore the fact that passing lambdas like this merely changes where the IF or SWITCH statement is made. I can agree that passing functions instead of booleans is better and more general. But pretending that IF/SWITCH are thus avoided, is delusional. For instance, at some point there will be a decision made whether the string matching must be case sensitive or not. If the program can do both at runtime, the IF will be, perhaps, in the main (or equiv.).
- buffyoda 10y agoIndeed, that's the whole point of inversion of control, is pulling the control out of the caller and into the callee. That's the primary reasoning benefit of functional programming.
- AWildDHHAppears 10y agoYou can go a long way without Ifs in a pattern-matching language like Prolog or Erlang, too.
- nn3 10y agotl;dr: prefer callback hell instead of straight forward ifs and somehow that's progress.
- Scea91 10y agoYeah, and the true fun starts when you try to debug it. Debugging streams in java is nightmare compared to debugging the same logic written in a simple foreach loop with a bunch of IFs.
- basicplus2 10y agosounds like what's really being said is.. It is recommended that programmers use abstractions whenever suitable in order to avoid duplication, and associated errors
- rosalinekarr 10y agoonly a sith speaks in absolutes
- kazinator 10y agoProblem is, a decision has to be made somewhere about which function to pass into that "if-free" block of code. The if-like decision has just moved elsewhere. That is a win if it reduces duplication: if a lambda can be decided upon and then used in several places, that's better than making the same Boolean decision in those several places. Programs that are full of function indirection aren't necessarily easier to understand than ones which are full of boolean conditions and if. The call graph is harder to trace. What does this call? Oh, it calls something passed in as an argument. Now you have to know what calls here if you want to know what is called from here. A few days ago, there was this HN submission: https://news.ycombinator.com/item?id=12092107 https://news.ycombinator.com/item?id=12092107 "The Power of Ten – Rules for Developing Safety Critical Code" One of the rules is: no function pointers. Rationale: Function pointers, similarly, can seriously restrict the types of checks that can be performed by static analyzers and should only be used if there is a strong justification for their use, and ideally alternate means are provided to assist tool-based checkers determine flow of control and function call hierarchies. For instance, if function pointers are used, it can become impossible for a tool to prove absence of recursion, so alternate guarantees would have to be provided to make up for this loss in analytical capabilities.
- bunderbunder 10y agoAll valid points. If you're going to do this sort of thing with much success, you really need to have a language with a fairly powerful type system. If function pointers are your only option for higher-order programming, I wouldn't even try. First class functions or interface polymorphism help, but I'd also want to have a language that makes it relatively easy to create (and enforce) types so that your extension points don't end up being overly generic.
- thaumasiotes 10y agoWhat distinction are you drawing between "first-class functions" and "function pointers"?
- bunderbunder 10y ago
- jwatte 10y agoThe first problem is that the "match" function is considered in the first place. It's too general. It should only be used in higher order constructs where its flexibility is actually needed. Second: The enum based refractor is actually valuable and fine IMO. If you need string functions, stop there. Now, shipping control flow as a library is a cool feature of Haskell. But, if those arguments are turned into functions, the match function itself isn't needed! It just applies the first argument to arguments 3 and 4, then passes them to the second argument. match :: (a -> b) -> (b -> b -> Bool) -> a -> b match case sub needle haystack = sub (case needle) (case haystack) Does that even need to be a function? Perhaps. But if so, it's typed in a and b and functions thereof, and no longer a "string" function at all. And, honestly, why are you writing that function? Typing it out where you need it is typically less mental impact, because I don't need to worry about the implementation of a fifth symbol named "match." sub (case needle) (case haystack)
- smoothdeveloper 10y agoPaul Blasucci had a good talk on Active Patterns (an F# language feature): https://github.com/pblasucci/DeepDive_ActivePatterns https://github.com/pblasucci/DeepDive_ActivePatterns This feature allows to encapsulate conditional matching on arbitrary input and dispatching. For those who know ML, it is making the concept of pattern matching extensible to any construct.
- dingleberry 10y agoi can't think of use of 'if' in a math function; however, if is implicitly used in input, say 0<x<1, f(x)=x, 1<x<3, f(x)=x^2 i see a lot of loop though, summation is so a double integral is loop within loop. i can't think a code analogue with derivative fta, i take that if in function body makes an ugly code.
- gmfawcett 10y agoLots of math functions are defined with 'if' -- the absolute value, the Heaviside step function, etc.
- samastur 10y agoThat's because you are thinking of continuous functions. You have actually gave an example of a non-continuous one, it's just that you think of it as two functions connected with input conditional. Other replier already gave you some common example, but let me add another one: signum(x), which returns if number is negative, positive or zero.
- nialv7 10y agoSomeone found a hammer, and now everything looks like thumbs
- vittore 10y agoWhen I read things like "anti-if" I recall this brilliant illustration that I saw several years ago - http://blog.crisp.se/henrikkniberg/images/ToolWrongVsWrongTool.png http://blog.crisp.se/henrikkniberg/images/ToolWrongVsWrongTo...
- yawaramin 10y agoview-source:http://antiifcampaign.com/ http://antiifcampaign.com/ Find in page: 'if(' 2 hits. So, yeah.
- oliv__ 10y agoIt’s no wonder that conditionals (and with them, booleans) are so widely despised! They are?
- drauh 10y agoGranted, I'm a mostly self-taught programmer, but I would have thought that if something appears in formal logic,[0] it should have an analog in a programming language. Even standard algorithms like quicksort[1] use conditionals. And, while I can see how massive switch statements suck, normal conditionals are common in everyday life: "If they don't have a dark roast coffee, get me a medium roast." All of which is to say, I really don't understand what he's getting at. The last example he gave seemed to make things even more complicated, and it basically renamed "true" and "false" to more descriptive things (forRealOptions, dryRunOptions), which seems to my untrained eye to boil down to the moral equivalent of a C enum. [0] https://en.wikipedia.org/wiki/Material_conditional https://en.wikipedia.org/wiki/Material_conditional [1] https://en.wikipedia.org/wiki/Quicksort#Algorithm https://en.wikipedia.org/wiki/Quicksort#Algorithm
- ta0967 10y ago> normal conditionals are common in everyday life: "If they don't have a dark roast coffee, get me a medium roast." "They had dark roast so I got you nothing as requested." IOW, this program is either incomplete or wrong. Cf. "Get me the darkest roast they have." - ifless, concise, robust.
- oliv__ 10y agoWhat if I want a vanilla latte instead?
- kowdermeister 10y agoSo if it's an undrinkable mud you are still happy, code executed perfectly :)
- ufo 10y agoI'm surprised that the article and none of the comments so far mentioned the "Expression Problem": http://c2.com/cgi/wiki?ExpressionProblem http://c2.com/cgi/wiki?ExpressionProblem Basically, if you structure the control flow in object oriented style (or church encoding...) then its easy to extend your program with new "classes" but if you want to add a new methods then you must go back and rewrite all your classes. On the other hand, if you use if-statements (or switch or pattern matching ...) then its hard to add new "classes" but its very easy to add new "methods". I'm a bit disappointed that this isn't totally common knowledge by now. I think its because until recently pattern matching and algebraic data types (a more robust alternative to switch statements) were a niche functional programming feature and because "expression problem" is not a very catchy name.
- jake-low 10y agoI was familiar with the problem but didn't have a name for it; thanks for providing me with one. What kind of work has there been on creating programming paradigms that make it easy to both add new types and new methods? Is it a CAP-theorem-type problem where every solution is a trade-off, or is there a way to have your cake and eat it too?
- chenglou 10y agoThere are languages (libraries) that solve it. For reference, check Clojure's multimethods and OCaml's polymorphic variants.
- smallnamespace 10y agoThe tradeoff is that techniques like multimethods significantly weaken the contract that people normally expect from methods/classes. For example, if I'm writing a normal Java class, I know where I go to find methods dealing specifically with instances Foo (namely Foo and its children and direct users); with multimethods, it's more likely that there is some multimethod out there in an unrelated class that looks for instances of Foo.
- galaxyLogic 10y agoIsn't this exactly the Smalltalk way? In ST what looks like if-statements actually are messages passed to instances of Boolean, with lambdas (in Smalltalk: BlockClosures) as argument. The boolean then makes the decision whether it will evaluate the lambda or not.
- svanderbleek 10y agoI think pattern matching is fine, I don't see how it is still "boolean". The additional techniques shown are interesting, but heavy abstractions that should not be prescribed in general.
- skybrian 10y agoGeneral principle: for every possible refactoring, the opposite refactoring is sometimes a good idea. So, yes, replacing booleans with a callback is sometimes a good idea. But in other situations, replacing a callback with a simple booleans might also be a good idea. Also, advice like this is often language-specific. In languages whose functions support named parameters, boolean flags are easy to use and easy to read. If you only have positional parameters, it's more error-prone, so you might want to pass arguments using enums or inside a struct instead.
- sqldba 10y agoUmmm. Many common day to day languages don't use lambdas. Also I have no idea what they are. So - yeah I don't think you can just replace if so easily.
- beisner 10y agoLambas are actually supported in most popular languages: C++, Java, C#, Go, JavaScript, even C. Sometimes they're called function literals or anonymous functions, but basically they involve creating a function without a name that can be passed around and executed. In some languages (Haskell, OCaml, etc) the anonymous functions can be extremely generic, whereas they are sometimes a bit less flexible in other languages. If you want a quick intro you can find one here: http://stackoverflow.com/questions/16501/what-is-a-lambda-function http://stackoverflow.com/questions/16501/what-is-a-lambda-fu...
- externalreality 10y agoI tried to ask the author the follow: (kept getting deleted as spam). Perhaps he will see it here but its unlikely due to the fact there are many comments as it is. Hi John, Are you familiar with Jackson Structured Programming? https://en.wikipedia.org/wiki/Jackson_structured_programming https://en.wikipedia.org/wiki/Jackson_structured_programming Notice how the focus in on using control flows that are derived from the structure of the data being processed and the processed data. Notice how the JSP derived solution in the Wikipedia example lack if-statements. Pattern matching allows ones to map control flow to the structure of data. What are your thoughts on that? I think inversion of control has other benefits but I don't think it has much to do with elimination of `if` conditionals, the pattern matching does that. Also, I noticed one thing: In the article you mention `doX :: State -> IO ()` as being called for its value and suggest that if you ignore the value the function call has no effect. Isn't it the case that a function of that type usually denotes that one is calling the function for its effect and not for any return value? Its value is just an unevaluated `IO ()`.
- mbrock 10y agoThe return value of the function is a description of an effect. Calling the function doesn't cause the effect to happen. That's why you could, for example, call the function many times and get a list of IO actions which you then execute in parallel or backwards or whatever. Hence "inversion of control".
- deleted 10y ago[deleted]
- externalreality 10y agoI was debating whether on not to put that last sentence because I knew that it would lead to a technical discussion that was aside from the meaning of the question. My question is more -- why choose an `IO ()` as an example of something being called for its value (especially since the article isn't aimed at a Haskell audience)
- 10y ago
- js8 10y agoThe idea that functional programming is a type of inversion of control reminds me of similar idea I had, when comparing OOP and FP. In OOP, you encapsulate data into objects and then pass those around. The data themselves are invisible, they only have interface of methods that you can apply on them. So methods receive data as package on which they can call methods. In FP, in contrast, the data are naked. But instead of sending them out to functions and getting them back, the reference frame is sort of changed; now the data stays at the function but what is passed around is the type of processing (another functions) you want to do with them. For example, when doing sort; in OOP, we encapsulate the sortable things into objects that have compare interface, and let the sort method act on those objects. So at the time sort method is called, the data are prepared to be compared. In FP, the sort function takes both comparison function as an argument, together with the data of proper type; thus you can also look at it as that the generic sort function gets passed back into the caller. In other words, in FP, the data types are the interfaces. So it is somewhat dual, like a different reference frame in physics. The FP approach reminds me of Unix pipes, which are very composable. It stands on the principle that the data are the interface surface (inputs and outputs from small programs are well defined, or rather easy to understand), and these naked data are operated on by different functions (Unix commands). (Also the duality is kind of similar to MapReduce idea, to pass around functions on data in the distributed system rather than data itself, which probably explains why MapReduce is so amenable to FP rather than OOP.) It also seems to me that utilizing this "inversion of control" one could convert any OOP pattern into FP pattern - just instead of passing objects, pass the function (method which takes the object as an argument) in the opposite direction. I am not 100% convinced that FP approach is superior to OOP, but there are two reasons why it could be: 1. The "nakedness" of the data in FP approach makes composition much easier. In OOP, data are deliberately hidden from plain sight, which destroys some opportunities. 2. In OOP, what often happens is that you have methods that do nothing rather than pass the data around (encapsulate them differently). In FP approach, this would become very easy to spot, because the function that is passed in the other direction would be identity. So in FP, it's trivial to cut through those layers.
- delian66 10y agohttp://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/m...
- based2 10y agoIf 'if' could support single 'expression' and multiple 'case's like 'switch/match', it would make easier the transition.
- rimantas 10y agoSandi Metz talks about ifs a bit here: https://www.youtube.com/watch?v=OMPfEXIlTVE https://www.youtube.com/watch?v=OMPfEXIlTVE
- sebastianconcpt 10y agoLess if is better, I agree on that. Lamdas technique is interesting because they "encapsulete" a specific case. In OOP this is achieved by using polymorphism on the objects instantiated for the right case. Right?
- dorfsmay 10y agoSince this is about FP, we have to have recursion: https://www.reddit.com/r/functionalprogramming/comments/4t913u/destroy_all_ifs_a_perspective_from_functional/ https://www.reddit.com/r/functionalprogramming/comments/4t91...
- mapleoin 10y agoThis is the best bit I think: > The problem is fundamentally a protocol problem: booleans (and other types) are often used to encode program semantics. > Therefore, in a sense, a boolean is a serialization protocol for communicating intention from the caller site to the callee site.
- MrManatee 10y agoIf I understood correctly, the article suggests that as a general principle you should replace your union types and case-by-case code with lambdas. I feel almost the opposite. Article: "In functional programming, the use of lambdas allows us to propagate not merely a serialized version of our intentions, but our actual intentions!" Counterpoint: The use of structured objects instead of black box lambdas allows us to do more than just evaluate them. For example, Redux gets a lot of power by separating JSON-like action objects from the reducer that carries out the action. But let's take instead the article's example of case-insensitive string matching. One tricky case is that normalization can change the length of the string: we might want the german "ß" to match "SS". Sure, the lambda approach can handle that. But now suppose that we want a new function that gives the location of the first match. It should support the same case-sensitivity options (because why not?). But now there is no way to get the pre-normalization location, because we encoded our normalization as a black box function. Case-by-case code would have handled this easily.