Live data from Hacker News

Destroy All Ifs – A Perspective from Functional Programming

degoes.net

201–210 of 226 posts

Re: Destroy All Ifs – A Perspective from Functional Programming

#201

Earlier quoted context omitted.

About the same distinction as I'd draw between an integer and a pointer to an integer.

Both function pointers and "first class functions" refer to function indirection. In its treatment of expressions, C doesn't draw the distinction between function and pointer to function. When you call printf("foo\n"), the printf part is a primary expression which designates a function, and evaluates to a function pointer. That pointer is then dereferenced by the postfix (). The main difference between a "first class…

>The main difference between a "first class" function and a function pointer is that a first class function carries an environment

Isn't that specifically a closure? I think first class functions have a more general definition.

Re: Destroy All Ifs – A Perspective from Functional Programming

#202
post #193

Earlier quoted context omitted.

The point of tail recursion is using constant space, not constant stack space (does Haskell even have a stack?) Anyways, the Haskell spec allows foldl' to use linear space just like its lazier counterparts. The fact that it uses constant space is an implementation detail of GHC. Reference: https://github.com/quchen/articles/blob/master/fbut.md#seq-d... Structural recursion always terminates in SML. Supporting infinit…

> does Haskell even have a stack? Yes

While this is true, the contents of the STG stack aren't necessarily obviously related to the conceptual "call stack", right?

Re: Destroy All Ifs – A Perspective from Functional Programming

#203
post #5

Define 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…

Someone else addressed the details of your counter argument, but I'd like to respond to it generally. It seems like every time someone writes an article on how to write better code, there are responses about how it doesn't make sense when taken to some logical extreme, or some special case, as if that invalidates the argument. (FP techniques in particular seem to provoke this.) But code design is like other design di…

I think the problem is that most of these types of articles don't take your advice - the "broken" code that they are improving is absolutely wrong, and there's no room for contextual arguments whatsoever. I mean, the article we're discussing is on the topic of eliminating conditionals wherever possible - that's a hardline stance against something so commonplace in programming it's hard to imagine working without it.

Re: Destroy All Ifs – A Perspective from Functional Programming

#204
post #157

Earlier quoted context omitted.

Why can't it be written as: .map_err(to_string) When using the lambda the type is inferred, so why should there be a need for the ToString?

If `to_string` was a function that was imported into the local namespace, you could. But since it's a method, you can't; you need to provide the trait name.

If there's a function and a method named to_string the user would've to be explicit about which one he uses by adding a namespace, like ToString.

If there's only one to_string function or method, the compiler could just take this one.

Re: Destroy All Ifs – A Perspective from Functional Programming

#205
post #199

The 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…

data 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.

Clarity 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.

Re: Destroy All Ifs – A Perspective from Functional Programming

#206
This 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.

Re: Destroy All Ifs – A Perspective from Functional Programming

#207
post #147
post #143

Earlier quoted context omitted.

> The more I write code the more I realize that the entire purpose of the code is to have some effect on reality, [...] Perhaps. But there's the effect you get from running the code on a computer. And the effect reading the code has on humans.

I'm not sure it's possible to write a piece of software that only you understand, that is of high quality (works reliably). I feel like code correctness (what a computer does with it) and readability (how well a human understands what is written) are two sides of the same coin. I think I would argue that if someone has happened to write a program that is impossible to understand for human readers (unreadable), and ye…

You can write a program that's hard to understand for humans, but comes with a computer-checkable proof of its correctness.

But aside from that pedantic possibility, I agree with most of the spirit of your comment.

Code can be useful to communicate with humans. In fact, the most expressive programming languages should be better at conveying precise descriptions of algorithms to other humans than natural language.

(Not a lot of programming languages reliably reach that ideal. Haskell sometimes comes close.)

I regularly write code that's meant to convey ideas, eg when explaining concepts, algorithms and data structures to people.

Re: Destroy All Ifs – A Perspective from Functional Programming

#208
post #174
post #135

Earlier quoted context omitted.

The problem with the Boolean is rather that people mix up the two values all the time. The lambda also has more type safety. A Boolean is always a Boolean, but the compiler (and in a dynamic language the runtime) can tell you when you are calling your passed functions with the wrong arguments, because you mixed them up.

Use an enum.

No need to stop there. Not only use an enum, but also make it bear different types of values for different cases, and you arrive at Algebraic Datatypes. Eg, for trees you can have:

    data Tree = Empty
              | Leaf Int
              | Node Tree Tree

Re: Destroy All Ifs – A Perspective from Functional Programming

#209
post #134

Earlier quoted context omitted.

What's the difference between multiple return values and returning a tuple? (Apart from that languages with multiple return values tend to have some special syntax for binding only the first few members of the returned tuple?)

The difference, in terms of type theory, is that a tuple is a product type [1] but a type representing multiple possible return values (to represent different outcomes) would be encoded using a sum type [2]. [1] https://en.wikipedia.org/wiki/Product_type [2] https://en.wikipedia.org/wiki/Tagged_union

I don't think eg Go uses a sum type:

https://gobyexample.com/multiple-return-values

Typically they use product types to simulate sum types.

Re: Destroy All Ifs – A Perspective from Functional Programming

#210
post #133

Earlier quoted context omitted.

I 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 funct…

Your 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 r…

Sure. I'd just like to have a nice set of operations to manipulate dicts that don't mutate and return the result, too.
Post reply on HN