trhawes
2019-4-6 11:23:57

I am mentoring the Racket track at http://exercism.io. I am working with a student to solve this problem, described here https://github.com/exercism/racket/tree/master/exercises/collatz-conjecture. Here is how I’d solve the problem: https://github.com/timotheosh/exercism-exercises/blob/master/racket/collatz-conjecture/collatz-conjecture.rkt

My student first submitted a solution similar to mine, but then thought this is better: (define (collatz n) (let/ec return (for/fold ([num n]) ([steps (in-naturals)]) (cond [(= num 1) (return steps)] [(even? num) (quotient num 2)] [else (add1 (* num 3))])))) My initial thought is that this code is less clear (maybe that has more to do with my own lack of familiarity with continuations?). Is this a good solution?

My view may also be tainted from this article: http://okmij.org/ftp/continuations/against-callcc.html

I understand that call/cc is not the same as call/ec or let/ec, but it still seems, to me, that this is overkill for the problem. Am I wrong in thinking this?


sorawee
2019-4-6 12:44:12

IMO, it’s less clear for sure. It’s also less performant than the straightforward implementation that didn’t use all these fancy features:

(define (collatz n)
  (let loop ([n n] [steps 0])
    (cond
      [(= n 1) steps]
      [(even? n) (loop (quotient n 2) (add1 steps))]
      [else (loop (add1 (* n 3)) (add1 steps))])))

Re: continuation, I would use it only when the alternative solutions require significant code restructuring that I feel it’s not worth making the changes.


jerome.martin.dev
2019-4-6 15:45:33

Hello Racketeers! When I want to assign a syntax-class to a syntax identifier, I can use (~var my-element my-class). If I want to access an attribute of that class, I can use (attribute my-element.my-attr). But is there another way, without using the dot notation? Something like (~attribute my-element my-attr)? The thing is, I’m constructing a macro from a macro, so I don’t know the identifier’s exact name, it’s inside a syntax variable.


jerome.martin.dev
2019-4-6 15:46:47

Right now I’m using (format-id #'my-element "~a.my-attr" #'my-element) to artificially create the dot notation, but I feel it’s a bit hacky.


alexknauth
2019-4-6 16:44:45

That’s what I’ve done, but you’re right, it would be much more convenient for macro-generating macros to be able to do something like (~dot my-element my-attr).


alexknauth
2019-4-6 16:53:29

Maybe you can define ~dot as a template-metafunction: #lang racket/base (require racket/syntax racket/string syntax/parse syntax/parse/experimental/template) (define-template-metafunction ~dot (lambda (stx) (syntax-parse stx [(_ x:id y:id ...) (format-id #'x "~a~a" #'x (string-append* (for/list ([y (in-list (attribute y))]) (format ".~s" (syntax-e y)))) #:source stx #:props stx)]))) Using it to generate a nested attribute name: (syntax-parse #'(m my-element my-attr) [(m elem attr) #'(attribute {~dot elem attr})]) Using it with ellipses for multiple nested attributes: (syntax-parse #'(m my-element my-attr1 my-attr2 my-attr3) [(m elem attr ...) #'(begin (define {~dot elem attr} 43) ...)])


jerome.martin.dev
2019-4-6 16:58:05

@alexknauth As usual, you’re my savior :wink: Thanks for that piece of code! I guess it would make a good PR!


jerome.martin.dev
2019-4-6 16:59:15

I never made template metafunctions before. It looks powerful but I guess a bit hard for the brain at times…


alexknauth
2019-4-6 18:17:39

jerome.martin.dev
2019-4-6 18:36:57

Looks good to me. I don’t know why you’re checking identifiers this way though. Was there a catch using :id ?


alexknauth
2019-4-6 19:02:51

syntax/parse wasn’t already imported into syntax/parse/experimental/template, and I didn’t want to change that


jerome.martin.dev
2019-4-6 19:10:25

Makes sense


samdphillips
2019-4-6 19:11:35

Technically Racket doesn’t have undelimited continuations.


samdphillips
2019-4-6 19:31:55

Using let/ec is more of an imperative style for sure. I feel in a majority of cases where iteration is occurring you should try and use the for family of macros.

For this specific case where you are returning the number of steps and not something out of the accumulator let/ec seems to be the cleanest way to write it using for/fold, unless you make a copy of the steps in the accumulator which would add some messiness.