3d scene representation in Haskell
devskypers.blogspot.com
devskypers.blogspot.com
Arguments for which approach is better aside, it is no wonder lots of people who learn and approach tasks like me, feel a little betrayed by Haskell. I've used it for a bunch of things, I'm past the steep learning curve - but it seems I'm still not reaping the rewards. Is my philosophy of jumping into projects really so bad? Is it Haskell's place to question such an approach, when it has served me so well elsewhere?
Haskell enforces a programmer to create structure. There's no way around it. And it's (nearly) impossible to hack things together without failing early and often.
As a result, before the problem you're trying to solve can be explored, you're stuck making guesses on how the problem should even be structured. You spend a few hours with one guess, it turns out to not get your far, and you try another.
The unfortunate part is that these guesses aren't letting you get to the meat of the problem effectively; you're stuck trying to solve a meta-problem.
Of course, once you have solved the meta-problem well enough, you can start exploring your problem. Unfortunately, you may find that your meta-solution doesn't actually let you answer questions you didn't know you wanted to answer from the get-go, and now you're back to square-one.
When everything is right, the program is usually very beautiful, safe, and—if you're skilled enough—efficient.
Perhaps not all Haskell programmers have this issue for very non-trivial programs, though I certainly do.
Those solutions might in another language require a hack or leaky abstractions to work, if they would be possible to get to work at all.
I've walked into problems like the OP has before with Haskell, and when there seems to be no nice way of defining a type structure to model the problem, it usually meant that the idea in my head had a fundamental problem. If I look at this tree I get the feeling that perhaps he just has two problems, and he's looking at it like he's got only one.
And once you do get your type structure neatly in place, often times the implementation will just flow out of your fingertips. And when it does, it will be powerful, flexible and robust.
The language itself if you don't venture too far into the depths of the latest research is quite comfortable and clean. The fact that I can express my thoughts effortlessly in a few keystrokes and that the types (ADT, generics, typeclasses) are so descriptive makes it my favourite language for day to day tasks.
At least for the time being... until a dependently typed language becomes usable.
Do you think to program or program to think? It seems like Haskell is biased to the former.
No, Haskell is probably even more fit here. Haskell is a godsend for prototyping! Because you throw any crap into the editor and with Haskell you'll know immediately what's wrong with said crap. And then when you refactor it later the compiler will guide and help you.
I feel like people just haven't got balls to see many compilation errors and prefer buggy, non-working, but “see! no errors, not like in those haskells!” code.
But in the meantime, you get the feeling you're making progress, even if you're not. I think that's the psychology of the matter.
I'm a Haskell fan (and still a newbie, unfortunately) and I sometimes fall prey to this feeling.
Please don't inject such language into technical discussions on Hacker News. The last thing we want is to swerve into low-quality flamewars.
Orangeduck, reikonomusha and others have posted fine comments in this thread. If they're wrong, the thing to do is show, not tell, that they're wrong. Then disagreement will make the discussion better instead of worse.
Code in Haskell can be so elegant, and examples of beautiful code are so abundant, that hacking together a bunch of hacks to see if they work doesn't seem like a conceivable way of doing things.
But of course, Haskell can be used that way to figure out and explore your problem. You can throw IORefs around, do everything with ugly IO effects, pass tons of parameters around, etc.
I don't think Haskell is at all designed for quick and dirty prototyping and exploration. The whole library and tool mindset is against that kind of developer activity.
Even after the service was up and handling hundreds of requests per second (yes, really - it was hardly the company's first foray, and we had a lot of customers lined up to use this as soon as it went live) we often found that the current design was severely hindering a new feature we wanted to add. So we'd just change the design. It wasn't uncommon for an update to require changes to 20% of the lines in 75% of the files. And it was no big deal. The compiler had our backs, and made sure we made all the changes in a way that made sense. We didn't always get it right - sometimes the system tests caught things the compiler missed, and on rare occasions a bug even slipped into production. Such is life in a startup, right?
But here's the thing. At no point in the process was there a monolithic design done ahead of time. In fact, it can hardly be said that there was any designing done ahead of time. Pretty much all the design work was a result of aggressive refactoring when adding new features. The whole process over years of development was nothing more than turning a quick and dirty exploratory prototype into a real, solid production system.
For contrast, an associated service was written using Rails, because we thought it'd be easy for what that service needed to do. It ended up being a nightmare to refactor and add new features. We were just never sure we got all the mechanical cases when something deep needed to be changed. Between the two, there's just no question which system was better when you start out not having any clue what the product needs to be.
More crucially, Haskell programmers tend live in their compiler and not their debugger. The adage "well typed programs don't go wrong" means you spend a lot of time just getting the code to compile, fitting types together, and not much time seeing how things work together. Much of this is because the community emphasizes the compiler, and in fact good graphical debuggers for Haskell aren't there yet (and even difficult to design given lazy evaluation). Bret victor didn't do his inventing in principle talk with Haskell for good reason!
Python and ruby. programmers live in the REPL and debugger, they don't get to observe type errors early like Haskell programmers do, but they immediately get to see real interactions. I can then see why going between Haskell and ruby would be a disaster.
It's not something I've ever needed. It's got little to do with quickly exploring design space.
Edit:
I guess I should include a couple words about why it's not really necessary in real coding. A side effect of good module boundaries is that you can test breaking changes in isolation in the REPL. Change a few things that are tightly coupled at the same time, load their module, don't worry about fixing any other modules until the first one works the way you like. In practice, this means you almost never need the option to defer type errors to runtime.
For instance, today I wrote tons of stuff in the IO Monad and had what you call 'real interactions'.
Also, more time spent in the debugger doesn't necessarily mean a faster working program. The psychology of it is more rewarding, but there's nothing necessarily more "real" about it.
I gave a talk recently, and I briefly explained how to decode a protocol buffer varint: if the last bit of a byte is true, shift the first 7 bits and repeat the process on the next byte. Does that map better to
getVarInt :: G.Get Int
getVarInt = G.getWord8 >>= getVarInt'
where getVarInt' n
| testBit n 7 = do
m <- G.getWord8 >>= getVarInt'
return $ shiftL m 7 .|. clearBit (fromIntegral n) 7
| otherwise = return $ fromIntegral n
or def read_varint(self):
run, value = 0, 0
while True:
bits = self.read(8)
value |= (bits & 0x7f) << run
run += 7
if not (bits >> 7) or run == 35:
break
return value
I'm perfectly happy saying it is equally well represented on both of them. I know I much prefer the first, but that's probably just because I prefer recursion to an explicit loop. From the sounds of it, you'd prefer the second, and I believe that's just because you prefer loops to recursion.This is very close to how every repeating process is taught and understood by folks. You take something, and repeat until there is a specific condition. Compared with a recursive solution. Which is of a few forms. One being to take your problem, break it into a series of identical problems and constantly restart with the results of having run a previous problem.
I grant that a recursive solution is not that much more difficult to phrase this way. "Take a dish, clean... start over unless there are no dishes/soap/whatever left." I just don't know if I have ever heard something stated this way.
Yet, sequential and direct control flow are common in human language, we know how to tell or be told to do something N times. Recursive formulations are harder to explain, and most people don't learn math as easily as they do language.
Simple recursions work exactly the same as the equivalent iterations. They branch on a condition, execute a step and repeat. How state is handled is the key difference. As Haskell has no concept of a variable, the only way to rebind a name is to recall a function.
I was going to say that a professional in our field who has problems with recursion and the basics of discrete math should take a look in the mirror. But maybe we have managed to raise the abstractions high enough so that one can be productive without knowing the fundamentals of computing. I probably need to broaden my concept of a professional in our field.
There is plenty of program writing to do that don't involve deep knowledge of maths, in fact I would say a vast overwhelming majority of work companies need to get done are not helped by, and possibly even hindered by, an intricate understanding and application of recursion (keep in mind, recursion is actually dangerous in strict languages). Most of us are more like police dectectives using well worn tooling and intuitive problem solving skills to get the job done. Sometimes we might even need the help of a mathemegician, but not most of the time.
I would argue that we use recursion quite a lot. For example, here's how clean a bunch of dishes. If the bunch is no more, your done. Otherwise, take a dish and clean it. Then clean the rest of the dishes like you did with the first one. We often leave out the end condition as it is often clear from the context.
I'm not advocating a deep knowledge of math as I know from experience that it has very few applications in software development. But to me a professional is someone who is not just skilled but knows the history and fundamentals of his trade. He knows not just that for (i = 10; i > 0; i--) terminates but also has an idea why that is so (and what it means that a program terminates).
Why would recursion be any more dangerous than iteration?
> Recursion is related to, but not the same as, a reference within the specification of a procedure to the execution of some other procedure. For instance, a recipe might refer to cooking vegetables, which is another procedure that in turn requires heating water, and so forth. However, a recursive procedure is where (at least) one of its steps calls for a new instance of the very same procedure, like a sourdough recipe calling for some dough left over from the last time the same recipe was made. This of course immediately creates the possibility of an endless loop; recursion can only be properly used in a definition if the step in question is skipped in certain cases so that the procedure can complete, like a sourdough recipe that also tells you how to get some starter dough in case you've never made it before. Even if properly defined, a recursive procedure is not easy for humans to perform, as it requires distinguishing the new from the old (partially executed) invocation of the procedure; this requires some administration of how far various simultaneous instances of the procedures have progressed. For this reason recursive definitions are very rare in everyday situations.
http://en.m.wikipedia.org/wiki/Recursion#Informal_definition
Recursion quickly causes a stack overflow when you get it wrong in a strict language (and sometimes even when you get it right!), iteration does not (rather, the CPU just spins and memory is safe). Also, iteration is intrinsically less expressive than recursion, meaning it follows to use it via the principle of least force.
doDishes [] = done
doDishes (dish:dishes) = clean dish >> doDishes dishes
Very much a recursion in my opinion.Recursion can cause stack overflow in lazy language too. But lazy or strict, that's more of an implementation detail although a very visible one. An iteration that appends to a list will run out of memory just the same. I also don't see how a recursion that runs out of stack space is any more dangerouse than an iteration that goes to an infinite loop.
Exhausting your call stack in most languages is really bad as far as debugging is concerned: it loses the ability to even form a decent error message (stack overflow), and you are left with poor content for diagnosing the problem. Exhausting the heap or having an infinite loop, in comparison, are vastly easier to debug (the latter being much easier than the former, thrashing the VM system is also a pain in the arse, though less so than exhausting the stack).
Also, the reason the Python code is a `while True` with an explicit counter and check at the bottom is because Python lacks a do-while loop
You have to front-load all the whiteboarding. Everything is easier (and usually bug-free) after that.
There have been many times where my inability to codify a mental model in haskell made me realize why that mental model was inherently wrong/unsafe.
newtype ID a = ID Int deriving (Eq,Ord,Show)
Should possibly just be: newtype ID a = ID a deriving (Eq,Ord,Show)