-
Notifications
You must be signed in to change notification settings - Fork 0
/
andrew-experience
141 lines (109 loc) · 6.33 KB
/
andrew-experience
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
Andrew's experience notes
=========================
Pros
====
Viterbi
-------
Writing an implementation of Viterbi was extremely natural in Haskell. In
particular, the Haskell syntax makes it extremely easy to match each case in
the Viterbi function with the corresponding mathematical recurrence relation
case. When debugging the logic of our implementation, this made things
considerably easier than in a standard imperative language.
In particular, this was magnified due to the fact that our Viterbi
implementation had to handle so many different base cases with differing
behavior.
Stochastic search
-----------------
Most forms of stochastic search actually have a very similar paradigm. Namely,
a set of initial guesses is formed, those guesses can be mutated, a new set of
guesses must be scored and either accepted if it performed well or rejected if
not, and finally, there must be some sort of termination condition.
These four logical chunks---initial guess, mutation, acceptance and
termination---were realized intuitively in Haskell as first class functions in
a record type called 'SearchStrategy'. With this grouping of first class
functions, we created a single higher-order search function that can accept any
one of a number of search strategies, where each search strategy has its own
unique SearchStrategy type. In fact, we were able to use Haskell's module
system to avoid redundant code and reuse functions in common to different
search strategies.
(N.B. In an imperative language with OO features like Ruby/Python, we probably
would have used some sort of class heirarchy to get this done. I feel like this
probably would have been a more verbose and overall less satisfactory approach,
but I'm finding it difficult to figure out how to articulate that.)
{NMD note: it would have been more verbose and clumsy because we'd be using a
system meant for encapsulating data as well as functions on those data; here we
ONLY wanted to encapsulate functions; the data did not change}
Well-typed programs don't go wrong
----------------------------------
We found that using Haskell cut down significantly on the number of run-time
errors. (In fact, run-time errors were almost exclusively reserved to some
variant of bad indexing.) Because of Haskell's strong and static typing system,
many logical errors are squashed at compile time when GHC informed us of a type
error. This makes debugging easier in some respects and allows us to be a
little bit more confident about the correctness of our code.
{NMD note: I have something similar in my index cards: our errors were either compile-time (type check) errors, or bad indexing runtime errors. Contrast that with segfaults, failed malloc, etc. in SMURF.)}
It's *fun* writing in Haskell
-----------------------------
There is a certain allure about writing elegant code with an elegant language.
While the brain processing required to write Haskell is still extremely high
for me, I've found the results to be immensely satisfying.
(N.B. This is another difficult one to articulate, but I really think some form
of "Oooo, writing code in a strong/staticly typed language is pretty" should
probably make it into our experience report.)
{NMD note: I think it's entirely appropriate to express our enjoyment at
programming in Haskell, even if it may only be due to a change of scenery!}
It's fast
---------
Haskell gives us the benefits of a higher-level language most commonly seen in
interpreted languages, but at the same time gives us speed comparable to that
of C or Java.
(N.B. Maybe this shouldn't be included, but I do really like the notion that
Haskell is high-level but also fast...)
{NMD note: I think it's important to note in the introduction that we
considered Haskell precisely because it's comparable to C/Java in performance
(along with ML)}
Cons
====
Run-time errors
---------------
The lack of any kind of back trace when getting run time errors (like out of
bounds errors) makes them incredibly difficult debug. We had to resort to ugly
trace statements to figure out where the errors were occurring in the code.
{NMD note: this and the wrapper functions are in my gripe notes, too!}
Input parsing
-------------
Input parsing was a bit cumbersome in that many of the types suited to hold the
input data had to be reconstructed or modified in some way before actually
using them in our program. In an imperative language, a lot of this can be done
as one is reading the input---but with Haskell we had to traverse our data
structures several times before it was in consummable form. (This isn't too
much of a negative since the input cost is insignificant relative to the
algorithmic cost of MRFy.)
{NMD note: we should focus on the programming irritation here. Not sure how
much is just due to PADS, but some is clearly due to difficulties dealing with
an externally specified representation (beta pairs) which could be a challenge
for others as well}
Compilation
-----------
Compiling an optimized version of MRFy required a decent machine. In fact, my
machine at Tufts---having only 2GB of memory and a dual core
processor---couldn't handle it all. (It runs out of memory.) I had to resort to
remotely compiling MRFy on my machine went home.
{NMD note: this is mostly Pads' fault due to using Template Haskell, I think.
Still worth pointing out: compile times are unfavorable compared to gcc}
Development time
----------------
Since both of us working on MRFy are inexperienced functional programmers, the
amount of time and brain processing to do tasks that would be otherwise trivial
in an imperative language was quite large. We found that in many cases, we had
to rethink our data structures and how they were formed---mostly due to a lack
of mutable memory in pure functions. (I think I worded this right, please
correct me if I'm wrong. Perhaps an example would be good, but I can't think of
a specific one off the top of my head.) Therefore, it has taken us much longer
to develop MRFy than it probably would have taken us in C. (Especially
Ruby/Python, but it wouldn't be good for MRFy to be implemented in an
interpreted language.)
{NMD note: I shudder to imagine the uncaught incorrectnesses we would have seen
in a dynamic language, but that aside, I think we should focus on the example
of handling a poorly represented but external (not under our control) input
format, namely the beta pairs.}