Live data from Hacker News

Boiling Sous-Vide Eggs Using Clojure's Transducers

blog.eikeland.se

21–28 of 28 posts

Re: Boiling Sous-Vide Eggs Using Clojure's Transducers

#21

(defn pid-transducer [set-point k-p k-i k-d] (fn [xf] (let [pid (volatile! (make-pid set-point k-d k-i k-d))] (fn ([] (xf)) ([result] (xf result)) ([result input] (vswap! pid (fn [p] (calculate-pid p input))) (xf result (:output @pid))))))) If guessing that the first 'k-d' on the third line should be a 'k-p'. I'm glad this bug didn't mess up your eggs!

Please take this in the ha-ha-only-serious spirit: I wonder if a static type system would have caught the error in this case...?

Probably not unless you make k-p, k-i, k-d types - as they are simply floats. Checking for unused function arguments however would have caught this error.

Running a linter (such as eastwood) in clojure does the trick:

  $ lein eastwood '{:linters [:unused-fn-args]}'
  == Eastwood 0.1.4 Clojure 1.7.0-alpha2 JVM 1.8.0_05
  == Linting pid.core ==
  {:linter :unused-fn-args,
   :msg
   "Function args [k-p (line 32, column 33)] of (or within) pid-transducer are never used",
   :file "pid/core.clj",
   :line 32,
   :column 1}
Sadly it only works if you actually use it, and I'm a lazy bastard :p

Re: Boiling Sous-Vide Eggs Using Clojure's Transducers

#22

Out of interest, why are you using a record for the PID, and not a map?

I had this grand plan of playing with core.typed, type annotate everything (ann-record Pid [set-point :- Number .....]). But the types made the examples too confusing for a blog post, so I dropped the idea.

The record stayed though - but there's no reason why it couldn't simply be a map.

Re: Boiling Sous-Vide Eggs Using Clojure's Transducers

#23

This is a really cool toy example of one of my favorite algorithms. For an algorithm that predates the transistor, I'm constantly blown away at its modern use cases. For example, dynamic pricing, used everywhere from airline tickets to parking meters, is nothing more than a variant of the pid algorithm. It is also used quite a bit in capacity allocation in a wide variety of industries. Instead of using NP Complete sc…

That is fascinating. Can you share some references on this?

Re: Boiling Sous-Vide Eggs Using Clojure's Transducers

#24
post #2

From the picture it looks to me like egg is undercooked i.e. the white is still runny and kind of mucousy, ideally you want a solid tender white and a totally separate uncooked yolk. A slightly higher temperature (67 deg C) would ensure the white properly solidifies whilst still being cool enough to avoid cooking the yolk. 45 minutes is unnecessarily long a long as he egg remains under 70 degrees not much will be hap…

62 and 63 are often viewed as ideal temperatures (for some definition of ideal), but yes one needs to accept some runniness of the whites.

Over at the Food Lab, Kenji did some research - via mixing methods you can manage to set the outer white at those temperatures and not just the inner white: http://www.seriouseats.com/2013/10/sous-vide-101-all-about-e...

Personally I normally cook mine to 63, and yes the white can sometimes not be perfectly set. As with everything there is some unit to unit variation.

Re: Boiling Sous-Vide Eggs Using Clojure's Transducers

#25
post #21

Earlier quoted context omitted.

Please take this in the ha-ha-only-serious spirit: I wonder if a static type system would have caught the error in this case...?

Probably not unless you make k-p, k-i, k-d types - as they are simply floats. Checking for unused function arguments however would have caught this error. Running a linter (such as eastwood) in clojure does the trick: $ lein eastwood '{:linters [:unused-fn-args]}' == Eastwood 0.1.4 Clojure 1.7.0-alpha2 JVM 1.8.0_05 == Linting pid.core == {:linter :unused-fn-args, :msg "Function args [k-p (line 32, column 33)] of (or…

If you used Cursive, it would have marked that parameter as unused right in your editor. Even lazy bastards get the advantages!

Re: Boiling Sous-Vide Eggs Using Clojure's Transducers

#26
post #22

Out of interest, why are you using a record for the PID, and not a map?

I had this grand plan of playing with core.typed, type annotate everything (ann-record Pid [set-point :- Number .....]). But the types made the examples too confusing for a blog post, so I dropped the idea. The record stayed though - but there's no reason why it couldn't simply be a map.

I believe you could also use the heterogeneous map (HMap) type if you wanted static typing:

    (defalias Pid
      (HMap
       :complete? true
       :mandatory {:set-point Num
                   :k-p Num
                   :k-i Num
                   :k-d Num
                   :error-sum Num
                   :error-last Num
                   :output-max Num
                   :output Num}))
And then use it as you might expect:

    (ann calculate-pid [Pid Num -> Pid])
There's no real downside to using a record, but they're really only necessary for polymorphism.

Re: Boiling Sous-Vide Eggs Using Clojure's Transducers

#27
post #22

Earlier quoted context omitted.

I had this grand plan of playing with core.typed, type annotate everything (ann-record Pid [set-point :- Number .....]). But the types made the examples too confusing for a blog post, so I dropped the idea. The record stayed though - but there's no reason why it couldn't simply be a map.

I believe you could also use the heterogeneous map (HMap) type if you wanted static typing: (defalias Pid (HMap :complete? true :mandatory {:set-point Num :k-p Num :k-i Num :k-d Num :error-sum Num :error-last Num :output-max Num :output Num})) And then use it as you might expect: (ann calculate-pid [Pid Num -> Pid]) There's no real downside to using a record, but they're really only necessary for polymorphism.

Thanks! I'm just getting started with core.typed, and record felt more natural as it's annotated just as you would annotate a function. I expected there to be a way to create a type a map just like that, but just haven't had the time to read up on it. So thank you for the example, will definitely play more with core.typed.

Re: Boiling Sous-Vide Eggs Using Clojure's Transducers

#28

This is a really cool toy example of one of my favorite algorithms. For an algorithm that predates the transistor, I'm constantly blown away at its modern use cases. For example, dynamic pricing, used everywhere from airline tickets to parking meters, is nothing more than a variant of the pid algorithm. It is also used quite a bit in capacity allocation in a wide variety of industries. Instead of using NP Complete sc…

PID is everywhere although for many real control systems the implementation isn't very sophisticated. I was surprised to learn that it only dates back to the 1890's (according to Wikipedia). Seems to me like the Greeks or Romans would have figured it out already.

The Greeks and Romans to my limited knowledge weren't very good at continuous maths. That whole domain of math seems fairly recent, at least in the western world. Calculus itself wasn't invented until Isaac Newton or a little before.
Post reply on HN