14 ms·
Implement with types, not your brain (2019)
- q3k 7y agoThis way of writing Haskell, from my experience, results in write-only code. While it's nice that the compiler helped you figure out which obscure operators to use for your code to actually compile, it sucks for the next person trying to find the bug in your code. And just because the types check out doesn't mean it's free from bugs. Code is also meant to be read and reasoned about by humans, not just by the compiler.
- thestoicattack 7y agoI agree that the result, while cool, is not readable. An obvious next step would be to demonstrate how the type-checked lambda-heavy implementation can be refactored into something that also makes sense to a human. > just because the types check out doesn't mean it's free from bugs That's certainly true in the general case. In fact, the author gives a type-checked-but-wrong example of "we need an Int, so just return zero." But in some specific cases, as the author writes, "One of those facts we can infer [from a type signature] is often the the only possible implementation." The article demonstrates that "possible" includes "also makes use of all the values available (or else the signature would be simpler)."
- eterps 7y agoIn less expressive statically typed functional programming languages this way of writing works better IMO.
- _bxg1 7y agoRich Hickey once said something (with regards to TDD) like, "We have guard rails on highways, but that doesn't mean we just take our hands off the wheel because the rails will keep us on the road. Guard rails don't know where we actually want to go. We drive the car where we're trying to go, and the rails are only there for when things go wrong." I think a version of this applies to static types as well. Even under normal circumstances, I've noticed at times my own thoughts getting lazy when refactoring statically-typed code. I don't "load" as much of the program into my brain as I would otherwise; I just change the part I want to change and then fairly thoughtlessly fix all the type errors. I don't think this is a good thing. The OP takes this to an absolute extreme. Static types are there to document and to catch bugs early; they cannot be used as a complete verification that your code is correct, much less a generative tool for writing logic where "you're not entirely sure how" it works.
- jerf 7y ago"I don't think this is a good thing." How good a thing it is depends on how right you are when you do it. If your language is statically typed, but not very well, then it's a bad thing. But it's not just abstractly a bad thing because you are a lazy person who didn't choose to do useless cognitive work, it's a bad thing because you may be wrong. To give a specific example, if your type system forcibly adjoins NULL to all pointer types, you can't afford to take your hands off the wheel when dealing with pointer types and neglect that case. You must think about it when using pointer types. On the other hand, if you take your hands off the wheel for a moment, but you perform only type system manipulations that you have good and correct reasons to believe won't affect the behavior of the underlying code... then what's wrong with that? I'm guessing you've used the former type of language more than the latter? Me too. I think you've turned the justifiable fear you feel in that sort of language into a heuristic, which makes it easy to then forget the underlying problems. But there's no particular virtue to having to understand more of the program to do a manipulation than having to understand less. Proof: If that was always true, then that recurses on itself until you shouldn't ever make a change until you understand the whole program completely in your head at once (if not the whole system it is running on). Even if that's true, it doesn't matter; we humans don't have the cognitive capacity for that, we must window our understandings. It isn't just the micro task of refactoring one particular function; it's completely common for me to undertake a type-driven refactoring of code where just the type-driven aspects are plenty to fill my cognitive window. Especially since I'm usually in a looser language and I really have to watch those darned forcibly-adjoined NULLs. If I'm somehow "obligated" to understand more, the result isn't that I'm going to have that understanding, the result is that my hand will be tied and I won't be able to do that refactoring because it'll blow out my cognitive budget.
- _bxg1 7y ago> we must window our understandings I'm not arguing with that. I'm only saying that we shouldn't assume "the types are enough", so long as we have more "cognitive window" available. Here's why: add :: Integer -> Integer -> Integer add x y = x + y add :: Integer -> Integer -> Integer add x y = x - y The type system doesn't have any idea which of these is correct. So if the programmer is "asleep at the wheel", it's still entirely possible for their logic to come out wrong. All I'm saying is we shouldn't needlessly ignore the actual logic in favor of "letting the compiler write it for us". That's what the OP is expressly advocating for.
- saityi 7y agoIt works best if you follow up the compiler assistance with a human-readability pass. Use the techniques in this post to guide you to a solution, which I find immeasurably helpful, then rewrite it by pulling it out piece by piece to make it readable with let/where blocks that describe the various steps.
- whateveracct 7y agoIn my Haskell experience, there's no substitute for Taste :)
- agentultra 7y agoCode is also meant to be read and reasoned about by humans, not just by the compiler. And thinking with types helps you to do that. Satisfying the type checker doesn't guarantee a program is functionally correct but it does reject a large number of obviously incorrect programs from being considered. And with holes it can guide use towards the likely correct program. From TFA: > I’m not going to say that blindly filling in type holes always works, but I’d say maybe 95% of the time? It's a tool. If you don't know how to implement something a good first approximation is understanding it's shape and filling in the holes. I think this is the spirit of what the article is demonstrating. Writing the program so that it's clear is the next step. The final example, `zoop` is renamed to `foldr` once we realize what we're working with. This is how I often work with Haskell in practice: I know the types and shapes of values I need to produce and I work with the compiler to write an expression that fits the type. Then I rewrite for clarity and intent.
- SquishyPanda23 7y agoI think to some extent, the preference of some Haskellers to write obscure code is orthogonal to having the types help your development. Most Haskell code I read in the wild is borderline incomprehensible because of things like poor naming choices. Typically I can rewrite the code to be clearer, but it's a lot of effort. I think Haskell would benefit from some style guidelines that emphasize that your code ought to be understandable by others. That would at least counteract the natural tendency to believe that terseness is a virtue or a goal. You can write incomprehensible C too. It's just that most people have stopped doing that.
- continuational 7y agoIt's quite common to see one or two letter variables in C too unfortunately, and typically they will have the most unhelpful type like "int" that says very little about it.
- chowells 7y agoI think you are talking about a different domain than the author. The author is talking about writing plumbing code - the parts where everything is reusable and multipurpose. In contrast, you seem to be focusing on the business logic portion of the domain where all complexity is necessary. In the reusable abstraction space, what you're talking about just doesn't happen. There aren't bugs that need to be hunted down by careful inspection in these things because the methods described in the article are sufficient to catch them. They'll fail to type check or have obvious flaws of the sort -Wall catches. In the business logic domain, there is a lot more room for errors that can't be caught by those approaches. But code in that domain also can't be written like this in the first place, so it's kind of a strange criticism to say "the code I can't write like this contains bugs that are hard to track down when I try to write it like this." I can sense one additional objection coming: "If this isn't for business logic, what value is it? I write business programs." To that, I would answer that most programs are stuffed full of redundant non-business logic, and you don't even realize how bad it is. It took roughly 30 years of c-like languages for people to be fully on-board with the idea that 3-clause for loops are error-prone boilerplate separate from the business logic and worth abstracting out. If you use Haskell enough you'll realize a lot of your code in most languages is there to fiddle with implementation details instead of working over the actual problem space. A lot of Haskell code takes the form of super-generic abstraction plumbing bits that serve to isolate a common pattern and make sure the edge cases are covered. Those bits of code are the ones usually called "impossible to understand", which are fully described by their types, and which can be implemented with this approach.
- whateveracct 7y ago> Those bits of code are the ones usually called "impossible to understand", which are fully described by their types, and which can be implemented with this approach. Once I let go of needing to understand how a piece of code turns into CPU instructions, I found that this code is actually the easiest to understand!
- kccqzy 7y agoEven these code that has nothing to do with business logic benefits from being readable, from being written with a purpose in mind. This is so that even when a typed hole suggests something fishy, you can immediately spot it. This is coming from something who tried that approach and had a bug in production.
- whateveracct 7y agoIf you think that about the first example in this blog post, then I think you're looking at the wrong part. This is code within a library that does one specific abstract thing. The term-level isn't important - look at the type signature. I am familiar with free monads/extensible effects, so I know the idioms at play in that 1-argument function. We're taking an extensible effect, "peeling" State off it (this is an idiom of EEs) and returning the rest of the effects within StateT. I can now this is a way to interop between the State effect in the effect stack & the StateT monad transformer. I don't give 2 shits about the implementation - I can use this to solve my problems just fine. EDIT: HAHA - I just realized that this function is called "hoist" which is exactly the name I would've given it based on common Haskell idioms. I didn't even read the name - just the type. That's the essence of Haskell :)
- tome 7y agoAs much as I am in favour of readable code, the point of the article is that you do not need to read the function! There is only one sensible thing that a function of that type can do. The type really is, in this case, the best technical explanation for what the code does. (One might want to add comments that help you understand the meaning but the implementation is really irrelevant.)
- agumonkey 7y agowhat about typed eDSL ? you get aided type fixing but above a human digestible ontology. only asking as I don't have experience in this
- abacate 7y agoAny language with a large enough lexical set or enough meta-programming capabilities can be classified as "write-only" from the point of view of non-experienced users. It's the everlasting battle between learning curve and expressiveness.
- cogman10 7y agoNot just Haskell. I've done a very similar thing in Java which I regret. The trick is to make sure that as soon as you've written this garbage, you go back, split things up into proper methods with descriptive names. Even things like introducing intermediate variables goes a really long way towards improving readability. To me, this is a starting point, not the finish line.
- monadic2 7y agoI don’t see this as much an effect of haskell so much as a younger culture of coding standards. I’ve certainly found trivially readable complex haskell problems before, but naming and library operators are a massive issue in the community.
- conjectures 7y ago> This way of writing Haskell, from my experience, results in write-only code. That's fine because you can enter zen state and refactor it using compiler hints ;)
- hopia 7y agoFor newcomers, the problem is exactly that they don't understand the type system. That's where soft documentation like examples would help a lot. I don't see why they would need to be mutually exclusive.
- z3t4 7y agoIf anyone can tell me what does functions does, I'll rewrite them in JavaScript.
- rovolo 7y ago> Finally we’re finished! A little experimentation will convince you that this zoop thing we just wrote is in fact just foldr zoop _ b [] = b zoop abb b (a : as) = abb a $ zoop abb b as It's difficult to understand the function because the author deliberately named everything just based on their type to show off this method of autocomplete. This is what the function would look like with the same names as ramda's ReduceRight: reduceRight _ acc [] = acc reduceRight fn acc (head : tail) = fn head $ reduceRight fn acc tail https://ramdajs.com/docs/#reduceRight https://ramdajs.com/docs/#reduceRight
- z3t4 7y agoJavaScript already have the reduceRight method on the Array.prototype. But here it is anyway as promised: function reduceRight(arr, value, reduceFunc) { if(typeof value == "function" && reduceFunc == undefined) { reduceFunc = value; value = 0; } for(var i=arr.length; i--; i<-1) { value = reduceFunc(value, arr[i]); } return value; } assert( reduceRight([1,2,3], (a,b)=>a+b) , 6); assert( reduceRight([1,2,3], 1, (a,b)=>a+b) , 7);
- z3t4 7y agoNext iteration looks like this. Note that I added another test when I made the update to the function. function reduceRight(arr, value, reduceFunc) { if(typeof value == "function" && reduceFunc == undefined) { reduceFunc = value; value = arr.pop(); } for(var i=arr.length; i--; i<-1) { value = reduceFunc(value, arr[i]); } return value; } assert( reduceRight([1,2,3], (a,b)=>a+b) , 6); assert( reduceRight([1,2,3], 1, (a,b)=>a+b) , 7); assert( reduceRight([1,2,3], (a,b)=>a-b) , 0);
- golergka 7y agoI learned some Haskell, and even wrote a couple of small programs and simple pull requests in it. However, I still wouldn't be able to effectively read and maintain such code at a reasonable pace. There are many reasons to love Haskell. The static typing, prevention of bugs, the whole ethos of "doing things the right way", the beauty of abstractions. But I don't think that I would be able to be really productive in it in a reasonably sized project. And code written in this blog post illustrates this very well.
- marcosdumay 7y agoYou shouldn't get downvotes, that's certainly a very common impression to get after writing small programs. But the ethos of the language is not "doing things the right way", it's "do it first, make it right while it grows". The biggest point of the language is on refactoring.
- mcphage 7y agoI get what you're saying, but defining a method from only a type signature, and then finding that it can be implemented by only caring about types, isn't much of an accomplishment. Of course you can define it by filling "type holes"—the method isn't real, so there's nothing it can or would do apart from the types matching up.
- whateveracct 7y agoWhat are you saying? The function at the top of this blog post does do real things. This method is real. You can't literally write all your Haskell this way, but I turn my brain off and play type tetris as a nontrivial part of my workflow pretty often. I rarely _only_ do that, but it's nice to not have to exert conscious mental energy and instead use tetris-like muscle/visual memory to make progress towards a goal.
- mcphage 7y agoI'm talking about the `jonk :: (a -> b) -> ((a -> Int) -> Int) -> ((b -> Int) -> Int)` extended example that you work through. Or take the haskell filter function: `filter :: (a -> Bool) -> [a] -> [a]`. Some googling says that there's no opposite function for filter, but it's present in a lot of languages, so let's pretend there is. The signature would be the same: `filterNot :: (a -> Bool) -> [a] -> [a]`. Just looking at the types, filling type holes, wouldn't distinguish those two functions—the difference isn't in the types, but in their behavior.
- whateveracct 7y agoThat's true (and mostly is due to the concrete nature of Bool) but even so, parametricity does help a lot implementing that function. `forall a.` cuts down on possible implementations, but you still gotta program sometimes :) In a more dependently-typed language, we can write something like this f1 :: (p :: a -> Bool) -> [a] -> [a `suchThat` p a] f2 :: (p :: a -> Bool) -> [a] -> [a `suchThat` not (p a)] and that would make it so f1 couldn't be filterNot and f2 couldn't be filter. And we get the added benefit of giving the caller of our function the proof that the elements have passed that predicate (maybe they can make use of that downstream) But even in this case, there's nothing stopping you from just always returning an empty list. Not all theorems come for free!
- gambler 7y agoThis article is a great example of how FP is currently being ruined in the same way OOP was ruined in late 80s. The paradigm had a lot of great ideas, but it became so popular so quickly that people began to fetishize its terminology and tools, while completely forgetting what those tools were meant to accomplish.
- Ididntdothis 7y agoVery true. And you should get really worried once we have FP consultants and executives talk about FP. This seems to happen to a lot of good ideas, be it OOP, Agile or TDD. Ideologues have very compelling messages but they usually destroy what they are promoting. They don’t allow the question “why are we doing this again? Are we on the right track?”
- agumonkey 7y agomainstream spoils everything
- whateveracct 7y agoWhat are the tools "meant to accomplish" exactly? I've deployed production code built with the exactly techniques in the article. it's doable and in my experience easy (modulo the large learning curve - pay it once, pay it forever) The (very simple all things considered!) top-level type signature of `Sem (State s ': r) a -> S.StateT s (Sem r) a` is also something I've used in production (different library of the same sort)
- atq2119 7y agoI wonder which fraction of the code actually benefits from these techniques though. Certainly I become suspicious from the fact that examples like in this article always tend to be of a very narrow, specific kind. In my experience, every worthwhile piece of code outside of certain libraries has enough of what you might call "business logic" that these theoretically clean approaches no longer apply to. This has been true across lots of domains, from games, to CRUD, to compilers, to combinatorial optimization algorithms. I'd go on to argue that the interesting core of all those problem domains was precisely in the areas that couldn't be solved using cute techniques like OP's.
- hcarvalhoalves 7y agohoistStateIntoStateT The function name means nothing, and the implementation is hard to follow.
- whateveracct 7y agoAre you familiar with Haskell? Or just a programmer in general? The name doesn't mean nothing. Either you're a Haskeller not familiar with monad transformer idioms, or you're not a Haskeller just shouting your uninformed opinion. Hoist means something very specific in the monad transformer world, and it really helps explain what this does. And the "StateToStateT" also is pretty literal (and in the types.) I'd actually argue this name is boring & completely derived from the types.
- gizmo385 7y ago> Either you're a Haskeller not familiar with monad transformer idioms, or you're not a Haskeller just shouting your uninformed opinion. This comment feels unnecessarily aggressive.
- whateveracct 7y agoSorry - I just have very little patience for commenters saying Haskell is "hard to understand" or "doesn't mean anything" when they clearly haven't even passed the prerequisites of understanding. When you don't have the prerequisites, the best thing to do is to be quiet and/or learn rather than pollute the comments with your unfounded value judgements.
- holtalanm 7y agoso, you're essentially gatekeeping people's opinions about the code, because they don't understand the core concepts of Haskell? I would argue that someone should be able to understand what a function does by reading the name regardless of their understanding of the underlying language it is written in. I've noticed this type of weird expectation of understanding is very prevalent in the FP world, which makes it _harder_ for newcomers to understand code at face value.
- myu701 7y agoDoes F# offer this 'type hole' thing? I admit I still like to let a type instance and then fill in its members with calculated values, database reads etc. but since I'm nice and C#rrupted I don't do it the idiomatic/right way, whatever that way is, I let a mutable type instance then modify and return it to whoever called the function.
- pansa2 7y agoThis is an extreme example of something I’ve found to be quite common: programming “types first”. For another example, I recently read a tutorial on parsing that started by defining `abstract class AstNode`, followed by multiple concrete derived classes. Only after ~100 lines was there any actual parsing logic. My approach would be the opposite - start with the logic, using built-in types, and maybe refactor to use custom classes later. Perhaps there are downsides to this approach, but it’s how I think about solving the problem. I’ve found this style of programming is easiest in languages that have powerful, flexible built-in types, which seem to be best provided by dynamically-typed languages.
- marcosdumay 7y ago> My approach would be the opposite - start with the logic, using built-in types, and maybe refactor to use custom classes later. That's how you discover what abstract logic you need. And then you implement that logic types first, because it's abstract, so the types are both enough to describe the functionality and much easier to reason about. And you go and refactor your old code by following the types (or the type errors, if you are more in the let the computer do the work mood). But I have no idea how to do that in dynamically typed languages. AFAIK, they hinder refactoring as much as they can.
- AnimalMuppet 7y ago> I’m not going to say that blindly filling in type holes always works, but I’d say maybe 95% of the time? I must do a very different type of programming. In my world, I'd say this might work maybe 20% of the time, max. [Edit: Others on this thread (lkitching and weevie, at least) say that the difference is that the types must be "sufficiently polymorphic". My types almost never are. I suspect that the difference is, the more polymorphic the types, the fewer details of what can be done with each type, and therefore the fewer the options on the ways they can be combined. For example, if my function takes two ints, I can add, subtract, multiply, divide, take the remainder, etc. I can't use this technique to guide that. But if I'm passed in two "a"s, I probably need to be passed in the function to use to combine them as well (they may not have a "+"). Then it's pretty clear - I'm passed in two "a"s, and a function that takes two "a"s and returns an a, and there's really only one way to combine those parts.]
- jfengel 7y agoOr, as it was put back in the 80s (and probably earlier), "Strong typing is for people with weak memories." https://multicians.org/thvv/realprogs.html https://multicians.org/thvv/realprogs.html
- AnimalMuppet 7y agoYeah, but back then, I didn't think I had a weak memory. The intervening decades proved to me that my memory is in fact weak.
- justincredible 7y agoReal Programmers is a joke meme.
- veeralpatel979 7y agoI think having a "complicated" type system like Haskell's is a good thing, because it lets you model data in your program accurately, and more importantly, lets you offload work from your brain to the compiler. One question: How well do languages like Haskell work for code with side effects, though? Say I want to write a CRUD web API. How easy is it to work with network requests and database operations, for example, in Haskell?
- tsimionescu 7y agoThe article does a very good job of illustrating why you should never program this way, if you read between the lines. More precisely, here is the damming piece of evidence: > Finally we’re finished! A little experimentation will convince you that this zoop thing we just wrote is in fact just foldr! Pretty impressive for just blindly filling in holes, no? Yes, that's pretty impressive. Instead of just using foldr, you've reimplemented it, and don't even know that! We also have no idea whether this code we've stumbled into is equivalent to foldr in terms of performance. If I knew you write code like this, I would feel compelled during code review to squeeze my brain to try to see whether there is some library function that does what you just did. Also, it's absurd to claim that these implementations are the only possible ones and that they are correct, when all you have is the pretty weak type system of base Haskell (even though it is probably the strongest type system in regular use!). Sure, for a->a there is a single possible implementation, but as soon as you have a list, for example, among your parameters, there are now arbitrarily many possible implementations, which differ either in meaning or in performance characteristics.
- christophilus 7y ago> a->a Even there, it's not true, right? It's been a while since I learned / used Haskell, but I think something that transforms a { name: 'bob' } to a { name: 'sally' } would fit that bill, right? So, there are many implementations which meet the type signature, but you'd be pretty surprised if your `identity` function did this.
- seek3r00 7y agoI can’t get pass the poor variables naming, though.
- Tehnix 7y agoThat's typically the case when dealing with very abstract functions: zoop :: (a -> b -> b) -> b -> [a] -> b zoop _ b [] = b zoop abb b (a : as) = abb a $ zoop abb b as Can barely be more precise than that, except for `zoop`, but that is obviously meant to be considered irrelevant to the point of the article. You will probably run into this in Haskell more than other languages though, because you can write a lot of general and abstract code in Haskell. In most other languages you cannot abstract over the types (higher-kinded types needed), and many still even lack polymorphism.
- 3fe9a03ccd14ca5 7y ago> (\(s', m') -> fmap swap I understand beauty is in the eye of the beholder, but this code, along with much of the Haskell code I’ve ever read, looks horrendous. Why overuse non-alpha characters? Nested parens take mental time to unwrap, and backslashes have a lot of meaning outside of a language.
- anentropic 7y agoI'd much rather have nested parens than "all the parens taken away" and having to remember and then mentally calculate the precedence order for a bunch of obscure operators, which is what I tend to see in a lot of Haskell code. No doubt it becomes natural after persevering for a while....
- riazrizvi 7y agoThis is one of those journeyman techniques that takes a lot of trial and error before you realize how helpful it is. Thanks so much for sharing your hard earned insight. I do get that people are complaining because the example is artificial and leads to a lot of complexity. I get why you did an artificial example, to show how universal and powerful the technique is. I think complaints in the comment section are inevitable.
- mirekrusin 7y agoTakeaway - haskell programmers will be replaced by machines first, js devs probably last. /s
- ameyv 7y agoI don't understand single thing in that code. Should it be so difficult for person from OOPs background? Disclaimer: Never coded in FP. But this looks hard to understand what it does first place. Anyway Nevermind.
- mumblemumble 7y agoIt doesn't seem to be meant to be easy for someone who isn't familiar with Haskell syntax, if that's what you mean. It doesn't seem like it would be too bad for someone who knows a dialect of ML. Maybe with the addition of a couple hints. I think, though, that, if it were written to be digestible for someone who's only familiar with OOP, it would quickly expand from being a blog post to being a small book.
- ameyv 7y agoIt seems that there are prerequisites for learning FP. Damn! :(
- Tehnix 7y agoI think a lot of people are missing the point of the article. People are complaining about the complexity of the function or similar, which is basically brushing off that some problem domains just are complex. Putting this into context, the author is demonstrating a function from his effect system library called Polysemy[0], which is a both complex concept and a complex problem to solve. To achieve this with his library, some complicated plumbing is necessary, and one of those functions happen to be `hoistStateIntoStateT`. The author's point is then summarised perfectly by: > Gee, that’s complicated! I must be really smart to have written such a function, right? > Wrong! I just have a trick! The author shows how you can, when posed with just knowing the type you want to achieve, more or less generate/infer the implementation from that. This is a common situation when stitching functions or parts of your program together, where you have an `A` and want to transform it into a `B`, given the functions you have available. [0] https://github.com/polysemy-research/polysemy https://github.com/polysemy-research/polysemy
- dragonwriter 7y ago> The author shows how you can, when posed with just knowing the type you want to achieve, more or less generate/infer the implementation from that. All this means is that, with a robust enough type system, determining the correct type is exactly as complex as determining the correct implementation. In some cases, in some languages, expressing the solution in the language of types may be more intuitive than and help guide the language of implementation, but, because they are essentially encoding each other, that's not only as subjective as any question of what is intuitive must be, but also an artifact of the particular type language and implementation language. In a different implementation language, the problem may be as (or more!) intuitive as it is in the type language in use.
- 6gvONxR4sf7o 7y ago>One question you might have is “what the heck does it mean for a type to be correct?” Good question! It means your type should be as polymorphic as possible. There's a mathematical sense in which this is correct, but I disagree for practical software. Yes, there's only one function of type (forall a. a -> a), so you can't screw it up if you have a type checker, while you can easily screw up a function of type (BizLogicType -> BizLogicType) since there are going to be many of those. But when reading code, the most polymorphic version of a function struggles from the same problem that using "x" as a variable name does. It's the type level version of the difference between ( x = f(g) ) and ( numBooks = countBooks(library) ).
- dang 7y agohttps://news.ycombinator.com/item?id=20278496 https://news.ycombinator.com/item?id=20278496
- ArchReaper 7y agoAs a non-Haskell-er, this article is impossible to follow. I have tried to learn Haskell a few times, always unsuccessfully. Haskell code is filled with so many Haskell-specific things. I realize the goal isn't to be an 'easy' language, but jeez. Aside from the horrendous variable naming, I'm having trouble understanding what the author actually accomplished, and why it's necessary for a tool like this to exist in the first place. Can anyone translate the problem into a more simplified, non-Haskell-specific version? > jonk :: (a -> b) -> ((a -> Int) -> Int) -> ((b -> Int) -> Int) I have no idea what this line means. To me, it looks like he wants to write a transformer that matches a specific signature, and he uses a tool that tells him which functions can fit the 'hole' he has in the code, based on types. If I'm correct (I'm assuming I'm not), how is this different than intellisense in an IDE that tells you a list of type-valid functions/variables? Why is this a necessary part of the Haskell coding process? My first instinct is that this demonstrates a problem with the language, if something like this is necessary. Appreciate anyone that tries to explain. I know most of my assumptions are probably wrong.
- eterps 7y ago> I have tried to learn Haskell a few times, always unsuccessfully I suspect that people wanting to learn Haskell actually want to learn a statically typed functional programming language. And if you also want that language suitable to everyday problems, the kind of things you are dealing with at work, then Haskell is probably not the best one to start with. I love how Scott Wlaschin presents statically typed functional programming languages as straightforward and pragmatic solutions to everyday problems: https://www.youtube.com/watch?v=PLFl95c-IiU https://www.youtube.com/watch?v=PLFl95c-IiU https://pragprog.com/book/swdddf/domain-modeling-made-functional https://pragprog.com/book/swdddf/domain-modeling-made-functi... Once you're comfortable with that (and the way it is presented here really isn't very complex) then Haskell becomes a lot easier to understand. But Haskell is very distracting because it is much more expressive and complex than than ML dialects for example, so it will be a big learning curve before someone is comfortable reading someone else's Haskell code. For other statically typed functional programming languages (f.i. OCaml/ReasonML, F#, Elm) that is not the case.
- q3k 7y ago
- gwenzek 7y agoGoing from the signature to the code took me less time than scrolling through this article. I'm not sure asking the compiler to babysit you every turn of the road is a good idea.
- beders 7y agoNo thanks. I like my domain model flexible and at run-time. You'll be better off in the long run. (Imagining having data for type A(v1) and type A(v2), for example) If you do static domain modeling, your world remains static. That's not real.
- justincredible 7y agoI would've just solved it directly: jonk ab aii = aii . (. ab) then because I'm a jerk: jonk = flip (.) . flip (.) or using Control.Arrow: jonk = (>>>) . (>>>) edit: formatting