Easy Solutions to Hard Problems?

I’ve been trudging along knee-deep in tree manipulation algorithms recently, all in Haskell. I admit, calling them “algorithms” makes me sound much smarter than I really am… what I actually worked on was converting one type of tree into another type of tree (functionA) and then back again (functionB), such that I would get back the original tree — all in a purely functional, stateless, recursive way. My brain hurt a lot. I hit roadblock after roadblock; still, I managed to go through each hurdle by sheer effort (I never studied computer science in school).

See, I have a habit of solving typical Haskell coding problems in a very iterative fashion. Here are the steps I usually follow:

  1. Research the problem domain (Wikipedia, StackOverflow, etc.)
  2. Take lots of loose notes on paper (if drawing is required), or on the computer (using emacs’ Org-mode).
  3. Write temporary prototype functions to get the job done.
  4. Test said function with GHCi.
  5. Repeat Steps 1 through 4 until I am satisfied with the implementation.

The steps usually work after one or two iterations. But for hard problems, I would end up going through many more (failed) iterations. Over and over again, hundreds of lines of Haskell would get written, tested, then ultimately abandoned because their flawed design would dawn on me halfway through Step 3. Then I would get burned out, and spend the rest of the day away from the computer screen, doing something completely different. But on the following day, I would cook up a solution from scratch in an hour.

It’s such a strange feeling. You try really hard for hours, days, weeks even, failing again and again. Then, after a brief break, you just figure it out, with less mental effort than all the effort you put in previously.

What can explain this phenomenon? The biggest factor is obviously the learning process itself — that is, it takes lots of failures to familiarize yourself intimately with the problem at hand. But the way in which the solution comes to me only after a deliberate pause, a complete separation from the problem domain, fascinates me. I call it the “Pause Effect” (PE), because I’m too lazy to dig up the right psychological term for this that probably exists already.

So, here’s my new guideline for solving really hard problems:

  1. Try to solve problem in a “brute-force” manner. Don’t stop until you burn out. I call this the “Feynman Step”, after a quote from the televised “Take the World from Another Point of View” (1973) interview, where, toward the end, he describes a good conversation partner — someone who has gone as far as he could go in his field of study. Every time I’m in this step, I think of how animated Feynman gets in his interview, and it fires me up again.
  2. Rest one or two days, then come back to it. This is the “PE Step”.

The best part is when you ultimately find the solution — it feels incredible. You get this almost intoxicating feeling of empowerment in yourself and in your own abilities.

Shinobu’s Haskell Coding Style (SHCS)

I’ve been coding in Haskell for a couple years. Now, Haskell does not really have an established coding style, like C (e.g., the excellent Linux Kernel Coding Style (LKCS)). This is partly due to the extremely flexible nature of the indentation syntax alone — Haskell enjoys the unique honor of having significant whitespace, but at the same time allowing the use of optional curly braces and semicolons for “imperative-styled” do-notation (do-notation is quite common in Haskell). I present to you my own coding style, aptly named “Shinobu’s Haskell Coding Style” — it stresses, above all, consistency and predictable indentation. Since I love the LKCS so much, I decided to use tabs — how you set the actual length of the tab is up to you and your editor-of-choice, but I personally use 4-space-wide tabs.

A picture is worth a thousand words, so here is a “picture” of SHCS in action, with ample pseudocode for your reading pleasure:

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
module Foo.Bar where

import Some.Standard.System.Library
import Some.Standard.System.Library2 (func)

import Foo
import Quux

-- | A haddock-compatible comment.
funcWithLongTypeSignature
	:: Eq a
	=> Int
	-> Int
	-> Int
	-> Int
	-> IO Int
funcWithLongTypeSignature a b c d = do
	let a' = foo a
	e <- blah a' b
	let
		c' = foo c
		d' = foo d
	f <- bleh c' d'
	-- A comment about (someTest f). Blah blah blah blah blah blah blah blah
	-- blah blah blah.
	when (someTest f)
		. func1
		. func2
		$ func3 arg
	when (someTest2 f)
		. func1
		. func2
		. func3
		. func4
		$ arg
	return $ e + f
	where
	e a b = do
		...
		return x
	f c d = case convert c of
		Z n -> return n
		_ -> return y
		where
		-- Break up 80+ character lines into parts.
		convert c = hoolaaloolaa'
			. hoolaaloolaa''
			. hoolaaloolaa'''
			$ hoolaaloolaa'''' c
		-- This version is also acceptable, if it increases legibility:
		--
		-- convert c = hoolaaloolaa'
		--	. hoolaaloolaa''
		--	. hoolaaloolaa'''
		--	. hoolaaloolaa''''
		--  $ c

someLongList :: [(Int, String)]
someLongList =
	[ (1, "a")
	, (2, "b")
	, (3, "c")
	, (4, "d")
	, (5, "e")
	, (6, "f")
	]

listComprehension :: [Int]
listComprehension =
	[ (a, b)
	| a <- [1..]
	, b <- [2..]
	, a + b < 100
	]

baz :: Int -> Int -> Char
baz a b c
	| a == 0 = 'o'
	| b == 100 = if True
		then if True
			then 'p'
			else 's'
		else 'q'
	| c == 22 = 'z'
	| otherwise = f 'k'
	where
	f c = blah $ bleh c


longStringConcatenation :: String
longStringConcatenation = "The quick "
	++ "fox "
	++ "jumps "
	++ "over "
	++ "the "
	++ "lazy "
	++ "evaluation."

data Geometry = Geometry
	{ geoGeo :: Int
	, geoGeoGeo :: Double
	} deriving (Eq)

instance Show Geometry where
	show Geometry{..} = show geoGeo
		++ show geoGeoGeo
		++ "!"

recordConstruction :: Geometry
recordConstruction = Geometry
	{ geoGeo = 1
	, geoGeoGeo = 2.0
	}

data SomeEnum
	= SEA
	| SEB
	| SEC
	| SED
	| SEE
	deriving (Eq, Enum, Ord, Show)

quaff :: SomeEnum -> Int
quaff s = case s of
	SEA -> 1
	SEB -> 2
	SEC -> 3
	SED -> 4
	SEE -> 5

WordPress renders the indentation tabs as spaces — sorry about that. Anyway, my greatest “triumph” was when I discovered that you could write the “where” clause at the same level of indentation as all the functions below it (e.g., functionWithLongTypeSignature). This reduced a lot of my code’s indentation, because I prefer to use where clauses wherever possible.

Here are the rules:

  • Place LANGUAGE extensions at the beginning of the file, with one extension per line.
  • Import standard, “system” modules first on its own block. Add all of your own modules on their own block below it. List all modules in alphabetical order.
  • 80 character limit. Try very hard to keep lines within 80 characters. This rule is especially pertinent to type signatures. Speaking of which…
  • Write explicit type signatures for all top-level functions. You may also want to write type signatures for functions underneath “where” clauses (but I think you have to use the ScopedTypeVariables extension to do this).
  • Use the “leading comma” rule when writing long lists or data structure definitions (e.g., the “Geometry” example above).
  • Indent long string concatenations (++).
  • Use GHC with at least the -Wall and -Werror flags.
  • When writing a case expression for a type that is like a C enum (e.g., SomeEnum above), explicitly write all the possible enumeration symbols for that type, instead of writing (n – 1) of them and using the “_” catch-all syntax at the end.
  • Use spaces between arithmetic components (e.g., “(n + 1)” instead of “(n+1)”.
  • Avoid use of curly braces/semicolons for do-notation blocks.
  • Avoid use of let statements, and instead use where clauses. The use of a where clause forces you to write in a top-down manner, with the “overall picture” first, followed by smaller component functions. I’d rather be faced with a one-liner function followed by 100 helper functions under a where clause, rather than first seeing the 100 helper functions in a gigantic let expression, followed by the one-liner.
  • Try to avoid nesting where clauses as much as possible.
  • When confronted with a long line, break it up into smaller parts (by function, if you’re composing many of them, or by argument, if it’s just one function with many arguments) and indent the “tail” of these parts, each on their own line (see “convert c” in the example).
  • Prefer to use the dollar symbol operator ($) instead of using parentheses. E.g., (foo $ bar x) instead of (foo (bar x)).
  • Instead of (foo (bar (baz (quux x)))), write (foo . bar . baz $ quux x). I believe this is an “unstated” style rule among Haskellers, as it is. And so I formally state it here.
  • Catch-all rule: if the code is short enough, it is optional whether you write it in one line or multiple (indented) lines (e.g., list comprehensions, lists, etc.)

These are just a handful of rules, but they go a long way to make things look consistent. And, because the indentation is predictable, it makes the code easier to read.

On Help Vampires

A help vampire (HV) is someone who asks for help to resolve a problem, especially on a forum or blog post, but:

  • does not read the forum posts/blog post,
  • provides little, if any, information about his/her particular situation (even if it’s drastically different than the one presented in the forum topic/blog post), and
  • provides no evidence of having spent even one ounce of energy to solve the problem, or to even narrow it down.

On top of this, a healthy HV usually:

  • has poor spelling and grammar,
  • (if in an e-mail thread) always top-posts,
  • communicates in terse statements, forcing others to assume 9 out of 10 variables to be a certain way in order for their statements, taken as a whole, to make sense.

I’ve been around in the Linux/FOSS world for several years, and I’ve seen many HVs. You can usually tell that you’re dealing with a HV because you’ll need to communicate with them with about 5 or 6 posts to actually get them to pose the right question — to just get to square 1. And by then, you’re so into the “yay, I’m helping someone!” feeling that you fail to realize the vampire fangs already dug deep inside your neck, sucking out every drop of your blood…

I see myself as a pretty nice person, because, I’ve helped out even the worst HVs, with the faint hope that one day, they would see the light and turn back into sane humans again. But, I think helping HVs is basically the same as feeding a troll — they will never learn.

Enough is enough. I refuse to waste time with people who cannot form coherent ideas in their heads!

There is so much information out there on the internet — with some simple double-quoting skills on google, you can find the solution to most things very quickly. For 99% of people, the answers are already out there, 99% of the time.

I declare, I will never feed the help vampires, on this blog or anywhere else!

P.S. I’m reminded of a reverse-HV situation, where the original poster of a forum thread, posts a very well thought-out question, but is met with answers from a mob of semi-illiterate people (some with very high post counts) who have either (1) not read the entirety of the OP’s post, or (2) not taken their medication. I’m glad that I’ve never had to face such mobs in my own threads…