In a
previous post,
pozorvlak and I wondered about the differences between the thought processes that goes into writing good static code, and those that go into good dynamic code. We figured that there wasn't a lot out there to help dynamic programmers get the hang of static style thinking, so what follows is a simple little toy example, solved in what I
(
Read more... )
Then there was the natural tendency for me to want to make the polymorphic type names correspond to each other; things were made easy due to your declaration of split looking like:
split :: b -> [b] -> [[b]]
Which made matching it up with foldl easy by just substituting [[b]] for a and not having to touch b. Whereas the natural tendency would be to write:
split :: a -> [a] -> [[a]]
Thus making the substitution in one's head a little trickier for a beginner when looking at foldl. I find that writing my own types using x/y/z for type parameters is easier.
There's precious few "thinking in types" tutorials that are actually accessible to beginners, so reading this was quite eye-opening. Keep it up! :)
Reply
I also discovered hoogle, which looks fantastic for finding matching types .. though I should probably get good at doing it in my head first. Still, I can't wait for an IDE with built in hooglitude :)
Reply
I think the closest we have to and IDE with built in hooglitude is GHCi on Acid as mentioned here. There's also vim integration, if you want it :)
Reply
I think other beginners might really like some elaboration on the thought process of matching the types up; it took me a few moments to realize first that split was point-free, that split was matching the types from the right, that the 'y' parameter was needed to fill in the second parameter of foldl to make the types perfectly fit, etc.
split sort of came out point free once I'd made the connection between its type signature and the fold one... First, I'll mention the "matching from the right":
In general, I think if you're writing function f such that f :: SomeInputs -> SomeMoreInputs -> AnOutput and you happen to have a function g lying around with type g :: SomeDifferentInputs -> AnOutput then g is a potential candidate for being the last thing called in your definition of f.
As for being point free, if that g has the type g :: SomeDifferentInputs -> SomeMoreInputs -> AnOutput then there's a chance you can make your f point free on SomeMoreInputs - if those latter inputs that g needs happen to coincide with what you're expecting in f. If that's the case, you've changed the nature of the problem - you don't have to write f :: SomeInputs -> SomeMoreInputs -> AnOutput anymore - you just need to write some (simpler) function h :: SomeInputs -> SomeDifferentInputs.
There's nothing to stop you from doing all this matching from the left though - if g happened to be of type g :: SomeInputs -> SomeMoreInputs -> IntermediateType and you can see that it'd be easy to write h :: IntermediateType -> AnOutput, then your definition of f is
f = g.h
Hm - I hope that didn't just get a whole lot less clear in the more general case ;) Maybe I should think about writing a concrete example of writing a function by matching types from the left?
Oh yes - you're right - I did choose the letter "b" in the type signature of split so as to make the connection to the type of foldl a little more obvious. I think this is similar to when you're writing untyped functions or procedures and you choose to make the parameter names match up:
//Forward declarations
void setBrushColour(colour);
void drawCircle(coords, radius);
void drawPrettyCircle(coords, colour, radius) {
setBrushColour(colour);
drawCircle(coords, radius);
}
...the fact that the parameter names match up makes the whole thing prettier, and easier to read and understand in a forum like this. But, once you've got a bit of practice in the language, third party libraries that use different names for the parameters in their documentation (position and size, perhaps) won't phase you at all.
Thanks for the feedback :)
Reply
Reply
Leave a comment