[racket] Lunch choosing program ported from Haskell to Racket
On Jul 4, 2014, at 2:42 AM, Matthias Felleisen wrote:
Thanks for the tips (comments below).
> On Jul 3, 2014, at 8:01 PM, John Clements wrote:
>> On Jul 3, 2014, at 8:52 AM, Brian Adkins <racketusers at lojic.com> wrote:
>>> Hi all:
>>>
>>> I've recently begun learning Racket. After making some progress through various books & tutorials, I took a shot at porting a non-trivial program I had written in Haskell to help choose a lunch place for a group of colleagues based on personal rankings of restaurants and individual history of visiting restaurants.
>>>
>>> The Haskell code was quite unpolished to begin with, so when you combine that with my very limited Racket knowledge, the ported code has much potential for improvement :) Both versions are in this gist:
>>>
>>> https://gist.github.com/lojic/d45437453ccc8bcba196
>>
>> Hmm, looks pretty good to me… except for the missing purpose statements on `rankrestaurants`, `rankUser`, and `rank`…
>
> I would work hard to squeeze the program into 102 columns, and I might eliminate local and let in favor of defined variables, thusly:
I have noticed that it's been a bit of a challenge keeping my code from creeping too far to the right with Racket - just the nature of lisp I suppose. I typically have at least a couple Emacs windows open in split-screen which gives me at most 98 columns when mobile or 116 when at my desk, so I usually try to stay w/in 98 columns just to avoid a wrap.
Just out of curiosity, is "102" a personal preference, or is there another reason for not exceeding that?
> ;; completeRatings adds a default (User, 1.0) rating to
> ;; restaurants so each restaurant has a rating from every user
> (define (filledOut ratings)
> (for/list ([user users]) (or (assoc user ratings) (list user 1.0))))
> (define completeRatings
> (for/list ([entry rawRatings])
> (match entry [(list restaurant ratings) (list restaurant (filledOut ratings))])))
I did consider whether some of my local functions should be at the top level. I generally favor explicit over implicit. The pros, for me, of promoting a function are:
* easier unit testing (or testing in the repl)
* being explicit about the data requirements, via args, of a function
* not creeping to the right so soon :)
* can be used elsewhere w/o refactoring if the need arises
* ?
The pros of local functions:
* explicitly communicating the function use scope
* more concise/readable code in *some* instances, e.g. w/ closures
* ?
The use of local functions did make testing the lunch code more difficult, but I think part of that was because I was simply porting and trying to match the Haskell code. IIRC I think I developed the Haskell code much more incrementally and tested the local functions via their outer function as I added them.
One concern w/ non-local functions is that, w/o a type checker, modifying a function signature requires finding all invocation instances and changing them. If a function is local, the scope is tiny. Maybe I'll find that proper use of modules will make finding/modifying function invocations easy enough for refactoring.
> I might also embed such things in a 'main' function.
>
> If there were a remote chance that the program would have to be used again, I would probably add (module+ test (require rackunit)) and I'd add tests for functions.
Yes, rackunit is on my short list of things to look into. I usually have decent test coverage in my professional code, but this was just a diversionary hack for friends :)
*So* much to learn with Racket, but I'm having a blast! Sincere thanks to those who have brought Racket to where it is.