5 ms·
The irony: "I write this not because I expect to break any new ground—all the techniques I use here are long-documented in the literature, and Haskell veterans
by axelerator 6y ago
The irony:
"I write this not because I expect to break any new ground—all the techniques I use here are long-documented in the literature, and Haskell veterans will probably find little new in this post"
Yet I can't get rid of the feeling the article is written in a way only "Haskell veterans" are able to follow it :-)
- Cu3PO42 6y agoI can't disagree with your feeling, but I think it's sometimes difficult to put yourself in the shoes of someone who doesn't have experience in a certain area. While explaining something to first-semester CS students, I said "but it obviously doesn't matter if you accept a pair as a single argument or two arguments". They, of course, asked why it didn't matter and I started off with "You see, functions are exponentials" and I was going to say "and normal algebraic laws apply", but realised after the first part that this way of looking at it surely wasn't going to help them without any background in more advanced theoretical topics.
- epsilonclose 6y agoWhat do you mean by "functions are exponentials"?
- Cu3PO42 6y agoA function with type A -> B is essentially equivalent to an object of type B^A. I can give you some relatively intuition for the case that A is finite. Imagine B^|A|, this is a list of B elements with one for every element in A. You can therefore interpret it as the return value for every possible input. Therefore it uniquely defines a function A -> B! The type of pairs is just the cartesian product. Therefore (A×B)->C ~ C^(A×B) = (C^B)^A ~ A->B->C. If you find this sort of thing, you might be interested in category theory :)
- uryga 6y agogoogle keyword: "exponential object" ("exponential function" clashes pretty badly ;) )
- epsilonclose 6y agoThank you, that helped!
- epsilonclose 6y agoThank you for clarifying. I was familiar with the notation A^B for the set of all f: A -> B, but this seemed like something more (and it does appear to be). Putting some links I looked at here to save someone a few seconds of Googling: https://en.wikipedia.org/wiki/Exponential_object https://en.wikipedia.org/wiki/Exponential_object https://ncatlab.org/nlab/show/exponential+object https://ncatlab.org/nlab/show/exponential+object
- dllthomas 6y agoI'm not sure if the notation you'd encountered differed, but in this case the equivalence is between A^B and B -> A (note the order). Interestingly, every identity you learned in grade school algebra which involved only addition, multiplication, and exponentiation holds here as an isomorphism. It can be a fun exercise to write the functions that take you back and forth. For instance: A^(B+C) = A^B * A^C to: (Either b c -> a) -> (b -> a, c -> a) to f = (f . Left, f . Right) fro: (b -> a, c -> a) -> (Either b c -> a) fro (f, g) = \case Left b -> f b Right c -> g c
- johnisgood 6y agoYeah, I do not think it would have been too pragmatic. If the lesson is mathematics, sure, but for CS it is more of a trivia, IMO, or at least I cannot seem to find what changes by knowing that piece of information.
- chriswarbo 6y ago> I cannot seem to find what changes by knowing that piece of information The most common change is that writing convertion functions between 'foo(bar, baz)', 'foo(bar)(baz)' and 'foo((bar, baz))' calling conventions becomes more annoying. It's not that I find myself doing it any more or less, it's that knowing they're equivalent makes it even more frustrating. There can also be a difference in efficiency, e.g. in 'foo(bar)(baz)' we can have 'foo(bar)' pre-calculate something expensive, which will get re-used if we do things like 'f = foo(bar); f(a); f(b); f(c); myList.map(foo(baz)); etc. whilst the 'foo(bar, baz)' version will generally re-calculate such things every time. Of course, this varies depending on compiler optimisations and other language features, but the nice thing about the 'foo(bar)(baz)' version is that it can simply be a matter of scope, e.g. in Haskell: foo1 x y = let cached = expensive x in ... foo2 x = let cached = expensive x in \y -> ... We can achieve a similar result in other ways and in many languages, but many of those alternatives (e.g. 'static variables', intermediate classes, mutable variables, etc.) require much more ceremony and boilerplate.
- johnisgood 6y agoWhat do you think about the Forth way of doing it? I cannot get into it in detail here, hopefully you know about its internals. If not, then I recommend 3 sources. - http://galileo.phys.virginia.edu/classes/551.jvn.fall01/primer.htm http://galileo.phys.virginia.edu/classes/551.jvn.fall01/prim... - https://www.forth.com/starting-forth/11-forth-compiler-defining-words/ https://www.forth.com/starting-forth/11-forth-compiler-defin... - https://www.forth.com/starting-forth/9-forth-execution/ https://www.forth.com/starting-forth/9-forth-execution/ > In Forth, there is virtually no excess overhead in recursive calls because Forth uses the stack directly. So there is no reason not to recurse if that is the best way to program the algorithm.
- noelwelsh 6y agoLeaving aside the technical content the post uses uncommon words and constructions that obscure its meaning. In other words it is overwritten. Taking the first paragraph as an example, you could make it much easier to read by: replacing "tenet" and "antithetical" with simpler words, removing the "of course", and breaking some of those run-on sentences into smaller sentences. I'm reminded of patio11's writing. There's a man who never uses one word when three will do. I expect I will now be excommunicated from HN. :-)
- arc0 6y agoNot really, it definitely assumes some background with Haskell, but it's far from 'veteran' level. All of the code samples except for the very last section only use elementary concepts.