Ep 030: Lazy Does It

Christoph’s eagerness to analyze the big production logs shows him the value of being lazy instead.

  • Last time: going through the log file looking for the mysterious ‘code 357’.
  • “The error message that just made sense to the person who wrote it. At the time written. For a few days”
  • Back and forth with the dev team, but our devops sense was tingling.
  • Took a sample, fired up a REPL,
  • Ended up with a list of tuples:
    • First element: regexp to match
    • Second element: handler to transform matches into data
  • (02:00) It’s running slower and slower, the bigger the log file we analyze.
  • “This is a small 4-person tech company.” “Where we can use new technologies in the same decade that they were created?” “Yes!”
  • Problem: No one turned on log rotation! The log file is 7G and our application crashes.
  • “I think we should get lazy.”
  • “Work harder by getting lazier.”
  • “Haskell was lazy before it was cool!”
  • Each line contains all the information we need, so we can process them one at a time.
  • (4:30) Eager and lazy is like the difference between push and pull.
  • Giving someone a big bag of work to do, or having them grab more work as they finish.
  • The thing doing the work needs a little more to work on, so it pulls it in.
  • Clojure helps with this. It gives us an abstraction so we don’t have to see the I/O happening when we do our processing.
  • When your map of a lazy sequence needs more data, it gets it on demand.
  • Clojure core is built to support this lazy style of processing.
  • File I/O in Java is lazy in the same way. It reads data into a buffer and when that buffer is used up, more is read.
  • Lazy processing is like a bucket brigade. At the head, you pour out your bucket and the person next to you notices the empty bucket and fills it up. Then this is repeated down the line as each bucket demands to be filled.
  • (07:55) Let’s make our code lazy.
  • Current lines function slurps in the file and splits on newline.
  • Idea: Convert it to open the file and return a lazy sequence using line-seq.
  • The return value can be threaded through the rest of our pipeline.
  • Each step of our pipeline is lazy, and the start is lazy, so the whole process should be lazy.
  • “It’s lazy all the way.”
  • Problem: We run it, and BOOM, we get an I/O error.
  • What happened? We were using the with-open macro, which closes the file handle after the body is complete.
  • Since the I/O is delayed till we consume the sequence, when we start the file is already closed.
  • “The ability to pull more I/O out of the file has been terminated.”
  • “Nothing at all, which is a lot less useful than something.”
  • (12:29) Rather than having a lines function, why don’t we just bring that code into the summary function?
  • Entire process is wrapped in a with-open so that all steps including summary complete before the file is closed.
  • Takes a filename and returns an incident count by user.
  • It does all that we want, but it’s too chunky. We’re doing too much in the function.
  • We usually want to move I/O to the edges and this commingles it with our logic.
  • “We just invited I/O to come move into the middle of our living room.”
  • I/O and summary are in the same function, so to make a new summary, we have to duplicate everything.
  • We could split out the guts, extract the general and detailed parsing into a separate function. For reuse.
  • This means you are only duplicating the with-open and line-seq for each new summary.
  • (16:29) How can we stop duplicating the with-open? To separate that idiom into just one place.
  • If you can’t return the line-seq, is there a way we can hand in the logic we need all at once?
  • Idea: Make a function that does the with-open and takes a function.
  • “Let’s make it higher order.”
  • We hand in the “work to do” as a function.
  • “What should we call it? How about process. That’s nice and generic.”
  • We turn the problem of abstraction into the problem of writing a function that takes a line sequence and produces a result.
  • Any functions that take and produce a sequence, including those that we wrote, can be used to write this function.
  • Clojure gives us several ways of composing functions together, we’ll use ->> (the thread-last macro) in this case.
  • As we improve the vocabulary for working with lines, our ability to express the function gains power and conciseness.
  • Design tension: if there is something that needs to be done for every summary, it can be pushed into the process function.
  • The downside to that is that we sign up for that for every summary, and that might not be appropriate for the ones we haven’t written yet.
  • We opt for making it easier to express and compose the function passed in.
  • We can still make a function that takes a filename and returns a summary, but the way we construct that function is through composition of transforms.
  • We can pre-bake a few transforms into shorter names if we want to use them repeatedly.
  • (23:20) We will still run into the I/O error problem if we’re not careful.
  • The function that we pass to process needs to have an eager evaluation at the end.
  • If all we do is transform with lazy functions, the I/O won’t start before the list is returned.
  • group-by or frequencies will suffice, but if you don’t have one of those, reach for doall.
  • “You gotta give it a good swift kick with doall.”
  • Style point: doall at the beginning or at the end of the thread? We like it at the end.
  • (26:07) We have everything we need.
    • Lazy so we don’t pull in the entire file.
    • I/O sits in one function.
    • We have control over when we’re eager.

Message Queue discussion:

  • (26:38) Long-time listener Dave sent us a code sample!
  • An alternative implementation for parse-details that doesn’t use macros.
  • Top level is an or macro.
  • Inside the or, each regex is matched in a when-let, the body of which uses the matches to construct the detailed data.
  • If the regex fails to match, nil is returned and the or will move on to the next block.
  • We tend to think of or as only for booleans, but it works well for controlling program flow as well.
  • The code is very clean and concise. And it only uses Clojure core.
  • “Without dipping into macro-land… Not that there’s anything wrong with that.”

Related episodes:

Clojure in this episode:

  • slurp, with-open, line-seq
  • ->>
  • or, when-let
  • map, filter
  • group-by, frequencies
  • doall
  • clojure.string/split-lines

Code sample from this episode:

(ns devops.week-02
  (:require
    [clojure.java.io :as io]
    [devops.week-01 :refer [parse-line parse-details]]
    ))


; Parsing and summarizing

(defn parse-log
  [raw-lines]
  (->> raw-lines
       (map parse-line)
       (filter some?)
       (map parse-details)))

(defn code-357-by-user
  [lines]
  (->> lines
       (filter #(= :code-357 (:kind %)))
       (map :code-357/user)
       (frequencies)))


; Failed Attempt: returning from with-open

(defn lines
  [filename]
  (with-open [in (io/reader filename)]
    (line-seq in)))

(defn count-by-user
  [filename]
  (->> (lines filename)
       (parse-log)
       (code-357-by-user)))

; Throws IOException "Stream closed"
#_(count-by-user "sample.log")



; Works, but I/O is coupled with the logic.

(defn count-by-user
  [filename]
  (with-open [in (io/reader filename)]
    (->> (line-seq in)
         (parse-log)
         (doall)
         (code-357-by-user))))

#_(count-by-user "sample.log")



; Separates out I/O. Allows us to compose the processing.

(defn process-log
  [filename f]
  (with-open [in (io/reader filename)]
    (->> (line-seq in)
         (f))))

; Look at the first 10 lines that parsed
#_(process-log "sample.log" #(->> % parse-log (take 10) doall))

; Count up all the "code 357" errors by user
(defn count-by-user
  [filename]
  (process-log filename #(->> % parse-log code-357-by-user)))

#_(count-by-user "sample.log")

Log file sample:

2019-05-14 16:48:55 | process-Poster | INFO  | com.donutgram.poster | transaction failed while updating user joe: code 357
2019-05-14 16:48:56 | process-Poster | INFO  | com.donutgram.poster | transaction failed while updating user sally: code 357
2019-05-14 16:48:57 | process-Poster | INFO  | com.donutgram.poster | transaction failed while updating user joe: code 357

Permalink

Eradicate Typos in Source Code

I don’t know about you, but I keep making typos like crazy1, so I’m really glad that today we’ve got so many tools to help us with their discovery and mitigation. While most text editors have some spell checking for the comments in source files2, that generally is not enough to free our source files from typos - often we’d make typos in identifiers, some team mates won’t be using spell checkers at all, some people would use British spellings instead of US spellings and so on.

Recently I found a really nice tool called misspell that really helps with solving such problems. It’s written in Go and is super fast. The basic usage is fairly simple3:

cider git:(master) $ misspell .
cider-debug.el:724:22: "untill" is a misspelling of "until"
cider-macroexpansion.el:47:55: "refered" is a misspelling of "referred"
cider-macroexpansion.el:48:32: "refered" is a misspelling of "referred"
CHANGELOG.md:927:9: "ouput" is a misspelling of "output"
cider-resolve.el:98:18: "referal" is a misspelling of "referral"
cider-resolve.el:99:33: "referal" is a misspelling of "referral"
cider-repl-history.el:414:32: "highlighed" is a misspelling of "highlighted"
cider-repl-history.el:421:33: "highlighed" is a misspelling of "highlighted"
cider-repl-history.el:429:36: "highlighed" is a misspelling of "highlighted"
cider-repl-history.el:434:32: "highlighed" is a misspelling of "highlighted"
cider-repl-history.el:574:3: "udpated" is a misspelling of "updated"
cider-repl-history.el:644:51: "highlighed" is a misspelling of "highlighted"
cider-stacktrace.el:656:27: "occuring" is a misspelling of "occurring"
nrepl-client.el:178:3: "Auxillary" is a misspelling of "Auxiliary"

It’s not easy to see this, but misspell found typos in both comments and source code identifiers. By default misspell allows both British and US spellings, but you can be more specific about your preference:

cider git:(master) $ misspell -locale US *.el
cider-cheatsheet.el:341:60: "cancelled" is a misspelling of "canceled"
cider-completion.el:179:52: "recognised" is a misspelling of "recognized"
cider-completion.el:181:36: "recognised" is a misspelling of "recognized"
cider-debug.el:47:20: "behaviour" is a misspelling of "behavior"
cider-inspector.el:42:20: "behaviour" is a misspelling of "behavior"
cider-eval.el:80:27: "behaviour" is a misspelling of "behavior"
cider-util.el:659:52: "cheque" is a misspelling of "check"
nrepl-client.el:427:15: "behaviour" is a misspelling of "behavior"
nrepl-client.el:567:46: "synchronise" is a misspelling of "synchronize"

I’m one of those people who learned English with British textbooks and for me that’s a mega useful feature, as I constantly opt for the British spellings, even though I know in Computer Science we’re not supposed to use them.

misspell can also fix the typos it finds, which is a huge time saver:

cider git:(master) $ misspell -w -locale US doc
doc/configuration/eldoc.md:25:0: corrected "behaviour" to "behavior"
doc/configuration/eldoc.md:33:4: corrected "behaviour" to "behavior"
doc/configuration/eldoc.md:35:36: corrected "Behaviour" to "Behavior"
doc/configuration/basic_configuration.md:85:0: corrected "behaviour" to "behavior"
doc/faq.md:49:38: corrected "favourite" to "favorite"
doc/clojurescript.md:183:32: corrected "favour" to "favor"
doc/clojurescript.md:221:4: corrected "favourite" to "favorite"
doc/repl/configuration.md:6:59: corrected "behaviour" to "behavior"
doc/repl/configuration.md:90:22: corrected "behaviour" to "behavior"
doc/repl/configuration.md:137:57: corrected "behaviour" to "behavior"
doc/repl/configuration.md:153:9: corrected "behaviour" to "behavior"
doc/repl/configuration.md:156:33: corrected "behaviour" to "behavior"
doc/repl/configuration.md:156:69: corrected "behaviour" to "behavior"
doc/repl/configuration.md:182:35: corrected "behaviour" to "behavior"
doc/repl/configuration.md:239:48: corrected "behaviour" to "behavior"

Like most such tools it’s not super reliable (meaning you’ll get some false positives here and there), but it has been very useful to me overall. If you’re an extra diligent person you can easily add it to your CI build and ensure that all typos are properly accounted for. The project’s FAQ is full of useful tips to get the most of misspell, so I encourage you to check it out.

That’s all I have for you today. Now let’s eradicate some nasty typos!

Permalink

HTTP Direct for Datomic Cloud



    Datomic Cloud is designed to be a complete solution for Clojure application development on AWS. In particular, you can implement web services as Datomic ions behind AWS API Gateway.

    The latest release of Datomic Cloud adds HTTP Direct, which lets you connect an API Gateway endpoint directly to a Production Topology Compute Group as shown below: 



    HTTP Direct provides better performance and simpler operation than AWS Lambda Proxy Integration. The latter option continues to be supported on the Solo Topology, which does not include the Network Load Balancer required for HTTP Direct.

    To try out HTTP Direct:
    1. Get the latest release of Datomic Cloud.
    2. Work through the HTTP Direct Tutorial.


    Permalink

    What is a functor?

    Functors are an operation that has a structure preserving property. But what is that? Are these things practical? Does it have anything to do with the real world? Of course! To be useful, it must derive from real-world things we see all around us. This one is an assembly line. How? That’s what this episode is all about.

    Transcript

    Eric Normand: What is a functor? By the end of this episode, I hope that you have a good understanding of this very useful idea and how it relates to the real world.

    My name is Eric Normand and I help people thrive with Functional Programming.

    Functor is an interesting idea. It’s a concept from category theory. It is used often in languages like Pascal and Scala where the communities do a lot of category theory stuff in their programs.

    Let’s go over what it is. Like some of the other algebraic ideas that we’ve gone over like associativity, monoid, communitivity, those kinds of things, this functor is about operations over a certain type that the operation has a structure-preserving property. We’re going to go over what structure preserving means.

    You’ll often find that the operation is called map or F-map.

    In Scala, the idea of functor is translated into mappable, I believe. It’s a mappable interface, a mappable…What do they call them? Case class or something like that. I’m not a Scala person so don’t take my word for that.

    A good example that you’re probably familiar with is mapping over a list. That is a functor. Map over a list is a functor. Now, what is that? What does that mean? Let’s use a metaphor. It’s a metaphor. It’s not a metaphor, but let’s call it metaphor for right now.

    Imagine that you are the manager of a factory. In this factory, you have an assembly line. Just to make it concrete, you’re making toy cars. In the car manufacturing process, there are steps that you have to follow in a certain order. You can lay out your assembly line to do those steps.

    When you’re doing factory management, you want to optimize the time it takes to make a car because the more cars you can make in a day, the cheaper they will be. You can service more customers.

    You can start to play around with things. You see that there’s a worker here on the one part of the assembly line, and they are adding the body of the car. There’s a chassis, they add the body. Then they pass that to a person who adds the wheels.

    Then they pass it to the next person. Maybe they pass it by hand, or there’s a conveyor belt or something. Regardless, person A adds the body, then person B adds the wheels. Now, you could say, “Let’s do an experiment. What if the same person adds the body and then the wheels?” You eliminate a person. You get rid of one person by combining these two jobs into one.

    Now, the question is, you’re doing this experiment to see if it’s faster, if it’s more efficient. It might be. It might not be.

    It’s a good assumption, but you’re assuming that the car will still be made correctly. That this person who is adding a body and then the wheels is doing the same work as two people where the first person adds the body and the second person adds the wheels.

    It’s the same work. You’re going to get the same car at the end. That is the structure-preserving property. That somebody doing two jobs in a row themselves is the same as two people doing the first job and the second job, respectively. That’s all the structure-preserving property is.

    Now, let’s translate this into mapping over a list. Let’s say you have a list of strings. You have two operations to do on these strings. The first is you have to trim the white space off the front and the back, so basically calling that trim. Then the second operation is to uppercase them, very straightforward.

    You can write what we might call a pipeline. You take the list of strings then you map trim over that, and then you map uppercase over that. The string values will flow through two different operations. One is trim. The next one, uppercase.

    I could say that’s inefficient because you’re making this intermediate list. The first map is making a totally new list, or array, or whatever it is in your language. It’s making a totally new list, and then that list is just thrown away right away after it gets passed to the second map. You’ve made some waste.

    Because of the structure-preserving property, you can do this — you can say, “Well, we’re only going to do one map. We’re going to make a new function for the map.” You pass enough function to map. This function is going to do both trim and uppercase.

    It’s going to take that as the string. It’s going to trim it, and then uppercase it, and then return it. You only run map one time. We also believe, just intuitively, that this is going to give us the same answer at the end with less waste. This is the structure preservation property.

    More formally, I could say that map of F on map of G of some list is the same as map of F composed with G over the list. It’s that function composition. We’ll talk about that in another episode. That’s basically what we do. We made a new function that does both operations. That’s structure preservation right there. That’s it. That’s all it is.

    You could also extend it and say there’s the idea that if I map the identity over the identity function over a list, then I get that same list out. That’s an important property. Basically what you’re saying is I have some neutral operation that does nothing to it. It shouldn’t change anything. This as a metaphor, I like to look at this like in a factory.

    I can put my car or my partially built car on a conveyor belt. That conveyor belt does nothing to the car except move it. It does not change the car in any way. That is like a neutral step. I can add conveyor belts between any two workers as much as I want. As long as those conveyor belts don’t damage the car or change the car in some way, I can just put conveyor belts between things.

    That is part of the structure preservation. I said that it’s kind of like a metaphor. It’s not really a metaphor. This is the structure preservation property that just exists naturally in an assembly line.

    That’s why it works. That’s one of the reasons why assembly lines are so powerful. You get the same car out at the end, and you can manipulate how the work gets done. How small tasks you’re making.

    I can give small tasks to all these people on the assembly line, or I can give bigger tasks to each person on the assembly line. As long as the tasks happens in the same order, I get the same answer. That’s pretty cool.

    This is what functional programmers find cool about the structure preservation property and functors.

    Let’s look at some other functors that you might not think of. We’ve got lists. Lists are functors because you can map, so map over list is functors. Now you might have, in your language, an idea of a maybe value or an optional value. That is also a functor. Even if you don’t have the idea, you might have something very much like it.

    I know in JavaScript, in Python, in Clojure, we’ve got this value that represents no value, so nil, or null, or nothing, or none. It has different names. You could squint and say, “Well, all values in these languages are actually optional.” They could be null, right? It’s well known that you get null-pointer exceptions and stuff if you don’t treat it like it’s optional, if you don’t check.

    You can write an operation that’s like map. It’s going to have this structure preservation property that takes the value. It does the check, is it null? If it is, it just returns null. If it’s not, it calls a function on it. That’s passed in.

    Some function F, that gets called on it. It’s going to have the same property, the same property that if I have a value and I map F on it. That’s a special map. It’s like a maybe map. It’s going to call maybe-map with F on this value, and then I call maybe-map with G on that value. It’s the same as composing them with a single maybe-map.

    That’s another common functor that you might have. Any collection could be a functor. In Clojure, we generally don’t treat things like that. Like a set, we have map over a set, but it treats it like a sequence. It picks some order to traverse the set in and maps over it that way. Then it returns a sequence.

    But you could have a map that’s just for sets, that takes a set and returns a set. Now you got to make sure that whatever you do, preserve that structure-preserving property. One way you could lose the structure-preserving property is sets have this interesting thing that they get rid of duplicates.

    If you have a bunch of strings and you trim them, you might lose some strings. The count will go down because now you have duplicates because you trimmed them. They’re now equal. What does that do? That might lose that property, that structure preservation, but maybe not. It really depends on the operations and the values that you’ve got in those sets.

    That’s functor. It’s an operation over a type. Usually, it’s called a map. That’s why in Scala, it’s called mappable. Map over a list is a functor. This property, like most category theory properties, it does show up in the real world. This is what math is. It’s descriptions of the real world.

    Sometimes they get really abstract, but they all derive from things we experience in the world. That thing that is very common is factory work with an assembly line has this property of preserving the structure of the car, or let’s say the structure of the work is preserved.

    Even if you split the tasks up into smaller bits or you combine them together, you still have the same car being made in this assembly line.

    That’s all I have. If you would like to see video, audio, or text versions of this episode, you can go to lispcast.com/podcast. You can subscribe there. You can listen to other episodes.

    You can find ways to contact me if you want to ask a question because I love answering questions on the podcast. I’d love to hear what do you think about this. If you’re into this, give me feedback about the topics you want to listen to, the kinds of formats you want. It’s all there.

    I’ll see you later. Or you’ll see me, or one of those. You’ll hear me. Whatever, see you later.

    The post What is a functor? appeared first on LispCast.

    Permalink

    The secrets of Type Inference in Clojurescript

    After watching this excellent talk by Mike Fikes about Type Inference in Clojurescript, I couldn’t resist the urge to share with the community what I have learned about this fascinating topic.

    The Clojurescript compiler has been granted the capability to infer types since version 1.10.516. The benefits for us, the cljs devs, are both at compile time and at run time:

    1. At compile time, our Clojurescript code is type checked automatically
    2. At run time, the generated Javascript code runs faster

    In a nutshell, type inference makes our code run faster with less bugs.

    Type inference saves time both for us the developers (we catch our bugs earlier) and our users (our code runs faster).

    Time

    Automatic type checking

    Currently, most of the automatic type checking occurs when we call a function that expects a number with a value that is for sure not a number. This type checking occurs at compile time, which means that we can catch some bugs without the need to even run our code.

    For instance, when we add two strings, we get a warning at compile time, saying that all the arguments to + must be numbers:

    (+ "hello " "my dear")
    

    Or if we try to compare “apples” and “oranges”:

    (<= "apples" "oranges")
    

    Code optimization around str

    In general, when a compiler knows that a value is of a certain type, it can generate optimized code (that runs faster), usually machine code.

    The same optimization occurs in the Clojurescript compiler regarding the Javascript code it generates. The cool thing about the Clojurescript compiler is that we can easily read the code that it generates and we can even compile Clojurescript code right in the browser.

    For instance, let’s take a look at the code generated by the str macro in two different situations:

    1. when the compiler knows that the arguments are all strings
    2. when the compiler doesn’t know that the arguments are strings

    When the compiler knows that the arguments are all strings, it uses this information to generate Javascript code that is as straightforward (and fast) as you can imagine. The arguments are joined with the empty string:

    (let [first-name "Kelly"
          last-name "Kapowski"]
      (str first-name last-name))
    

    However, when the compiler doesn’t know that the arguments are all strings, the str macro is forced to generate code that calls the str function from cljs.core namespace:

    (defn my-name [first-name last-name]
      (str first-name last-name))
    

    This additional function call has a performance cost. We can save it by letting the compiler know that the arguments to our my-name function are strings. This is called type hinting and this is done via a ^string metadata information before each argument. Now, the compiler generates optimized code without the additional function call:

    (defn my-name [^string first-name ^string last-name]
      (str first-name last-name))
    

    This type hinting is valuable but it comes at a price: it requires an extra effort from the developer and it makes the code more verbose.

    In some situations, the compiler is able to infer the type of a value without any type hinting. For example, the value returned by the str macro is for sure a string. As a consequence, if for some reason we call str on the value returned by my-name this superfluous call will be saved by the compiler:

    (defn my-name [^string first-name ^string last-name]
      (str first-name last-name))
    
    (str (my-name "Kelly" "Kapowski"))
    

    The type inference mechanism is smart enough to handle less trivial situations. For instance, when we call str on an if expression where both branches are strings, this superfluous str call is also removed from the generated Javascript code:

    (defn kelly-or-jessie [x]
      (str (if x 
             "Kelly"
             "Jessie")))
    

    Code optimization around truth

    Another area where code optimization occurs due to type inference is when we check whether a value is truthy or not. In order to fix the weird conception of truth in Javascript, Clojurescript wraps the predicate inside an if expression in a truth_ function call.

    This additional truth_ function call is what makes it possible to consider 0 as truthy.

    (if 0 "ok" "bad")
    

    While in Javascript, 0 is falsy:

    var a = 0? "ok" : "bad";
    a
    

    (This truth lesson is explained in greater detail here.)

    On one hand the truth_ function call makes our code more reliable, on the other hand it induces an extra cost in terms of performance. When the compiler knows for sure that a the if predicate is a boolean, we can save this extra cost.

    Take a look at this function, where we type hint the argument as a boolean:

    (defn to-be-or-not-to-be [^boolean x]
      (if x 
        "To be"
        "Not to be"))
    

    Sometimes, the compiler can infer on its own that a value is a boolean. For instance, the value returned by not is known to be a boolean. As a consequence when the if predicate is a not expression, the truth_ function call is saved:

    (defn not-to-be-or-to-be [x]
      (if (not x) 
        "Not To be"
        "to be"))
    

    Code optimization around boolean algebra

    For similar reasons to what we saw in the previous section about truth in Javascript, boolean algebra in Clojurescript induces fairly complicated Javascript code. Take a look for instance at the Javascript code generated for this boolean stuff:

    (defn boolean-stuff [x y z]
      (or (and x y) (and y z)))
    

    However, when the compiler knows for sure that all the arguments are booleans, it can rely on the native Javascript boolean operators. For example, if we type hint the arguments as booleans, the Javascript generated code is much more compact and runs faster:

    (defn boolean-stuff [^boolean x ^boolean y ^boolean z]
      (or (and x y) (and y z)))
    

    Limitations

    At the time this article is written (May 2019), type inference is a pretty new feature in Clojurescript. There is still a long way to go and many cool ideas are experimented by the Clojurescript core team. Be sure to watch this excellent talk by Mike Fikes if you want to know more about the history and the future of type inference in Clojurescript.

    There are two main limitations related to type inference:

    1. It makes the compilation a bit slower
    2. There are some edge cases where it causes a misbehaviour

    At the end of his talk, Mike shares more details about the impact of type inference on the compilation time.

    Let’s see here an example of an edge case where type inference causes the generated code to be buggy.

    Imagine you have a function foo that returns for sure a boolean:

    (defn foo [x]
      (not (not x)))
    

    And a function bar that uses foo as a predicate inside an if expression:

    (defn bar [x]
      (if (foo x) 1 2))
    

    Now, when we call (bar 0) we get 1 because in Clojurescript 0 is truthy:

    (bar 0)
    

    The problem occurs if we rewrite foo by getting rid of the double not, just like this:

    (defn foo [x] x)
    

    This redefinition of foo makes the code generated for bar buggy because at the time the ode was generated, foo was returning a boolean for sure. The compiler relied on this fact. But now, foo returns its argument as is - which may not be a boolean. If we pass to bar an argument that behaves badly as a boolean, our code will be broken (as we wrote earlier, in Javascript 0 is falsy):

    (bar 0)
    

    Conclusion

    Historically, the main focus of the development of Clojurescript as a language was to make it reliable even though it runs on top of an unreliable language like Javascript. Now that Clojurescript is mature enough, there is room for new initiatives like type inference that are beneficial both in terms of performance and in terms of type checking.

    Happy Clojurescript!

    Permalink

    PurelyFunctional.tv Newsletter 327: Tip: always be decomplecting

    Issue 327 – May 20, 2019 · Archives · Subscribe

    Clojure Tip 💡

    always be decomplecting

    Decomplecting is design. It’s a way to understand a problem better. And code is an encoding of the understanding we have of the problem. always be decomplecting asserts that our code should reflect the contours of the problem.

    Whenever I’m coding, I start to pull the code apart into smaller bits. I look for places where pulling it apart feels natural. I know that’s quite subjective, and require a lot of experience. But it’s also an iterative process, so it’s not something I get right on the first try.

    However, I have some questions I ask myself:

    • Can I imagine using this on its own? A thing that can be used on its own should probably be its own function.
    • Can I think of a good name for each of the parts? If there’s a good name, there’s a good chance it can live on its own.
    • What are the responsibilities of this code? Surely, there are ways to describe the responsibilities at different levels. Are all of those ways clearly expressed in the code?
    • What are the topics going on in this code? Sometimes, instead of looking at “responsibilities” or “actions”, “topics” gives a different perspective. For instance, “configuration” might be a separate topic from “storage”.
    • What are the semantic levels involved? Am I crossing different levels of meaning? For example, treating a hashmap like a hashmap, and in the next step treating it like a Person record? Separate those out.

    These questions help me feel my way around a refactoring. Do you have questions you ask yourself? Let me know and I’ll share them.

    RDD Course Launch 🚀

    Folks, if you are reading this, the course has launched! Repl-Driven Development in Clojure is now available for purchase at a 50% discount. That discount will last till the end of the week, so if you want to buy, now is the time. By the time the next newsletter comes out, the discount will be over.

    There were three new lessons this week.

    1. Editing servers and other long-running processes goes over some tips and techniques for applying RDD to servers.

    2. Some reloading tools some people use talks about clojure.tools.namespace, Component, mount, and Integrant. It describes the problems they are solving and when I think they should and shouldn’t be used.

    3. Recap and Conclusions wraps up the course.

    And those are all of the lessons. In total, there are nearly nine hours of video in this course. The course explains the background behind Repl-Driven Development (RDD), the practical nuts-and-bolts, and the high-level guidelines to keep you moving toward flow and deep engagement with your software.

    I have gotten so many thanks and advice from people who have followed along with the early access for the course. The course is better because of their questions, comments, and suggestions. I’m so excited by this course because people seemed to lap it up. It seems there aren’t enough demonstrations of real RDD in video form.

    And now it’s available at a serious discount, for one week.

    Of course, members of PurelyFunctional.tv get the course as part of their membership for no extra cost. Just another benefit of being a member.

    Check out the course. The first lesson is free.

    Clojure Media 🍿

    Clojure/north videos are coming out! You can watch them here (YouTube).

    Brain skill 😎

    Cross-pollinate. Learn outside of your normal bubble. Read about other programming languages. Read about math ideas. Read about biology, and poetry, and history. It exercises your ability to learn, and you never know what ideas will come out of unrelated fields.

    Clojure Puzzle 🤔

    Last week’s puzzle

    The puzzle in Issue 326 was to write a function to maximize the concatenation of integeres.

    You can see the submissions here.

    I think it’s cool that most of the implementations followed the same basic structure. A lot of these puzzles were defined for imperative languages where this kind of thing is a bigger challenge. But in Clojure, it’s just a single pipeline. Here is Steven Proctor’s solution, which is illustrative of the common solution:

    (defn maxcat [l]
      (->> l
        (sort-by str)
        reverse
        (clojure.string/join "")
        bigint))
    

    This week’s puzzle

    Largest Lynch-Bell number

    A Lynch-Bell number is an integer that is divisible by each of its base-ten digits. The digits have to be all different (no repeated digits) and it can’t have a 0 because you need to divide by it. For instance, 135 is evenly divisible by 1, 3, and 5.

    The task is to implement a search for the largest Lynch-Bell number. Feel free to get clever with reducing the search space if the search is not fast enough.

    As usual, please send me your implementations. I’ll share them all in next week’s issue. If you send me one, but you don’t want me to share it publicly, please let me know.

    Rock on!
    Eric Normand

    PS Newsletter Maintenance 🔧

    Folks, I migrated the email service I use for this newsletter. You’re now reading this on the new service.

    Email migrations can be tricky. I would really appreciate it if you report any issues with deliverability. That includes not receiving the email, the email showing up in spam, and any warning messages you get in your email client. I want to make sure you can get the emails you want.

    The most obvious benefit to you of the new system is that I am no longer tracking links you click on. Unlike my old system, my new system can turn off link tracking. You actually know where the link will send you before you click. And nobody is tracking the clicks.

    I also believe the new system has better deliverability, meaning you’re more likely to get it. I am still tracking email opens, because I would like to make sure that people are getting the email. Open tracking is the best way to assess the health of your email delivery system.

    The post PurelyFunctional.tv Newsletter 327: Tip: always be decomplecting appeared first on PurelyFunctional.tv.

    Permalink

    Why am I podcasting about functional programming?

    I received a negative YouTube comment. Normally, I ignore those, but this one insulted you, my audience. So I address it. Why am I podcasting about functional programming? What teaching techniques do I employ to help people learn?

    Transcript

    Eric Normand: Why am I podcasting? Hi, my name is Eric Normand and I help people thrive with Functional Programming.

    I got a comment on a YouTube video the other day, a couple of days ago. If you don’t know, I record this as a video and I post it as audio for a podcast video on YouTube. Also, I have it transcribed and I put that text on my website.

    I’ve got three different media coming out of this. I get comments from all sorts of places, but I got a comment on YouTube. Normally I get positive comments, but this one was negative. Even if I get negative comments I usually don’t make any deal out of them, but this one was different.

    This one didn’t just say my podcast or my videos make no sense or are worthless like a lot of them do. This one also insulted my readers and my listeners and my viewers, so I feel it needs to be addressed. I’m going to read it, this is a comment from YouTube user “Thor S.” This was in the video where I talked about monoids.

    [baby noises]

    Eric: Got my baby here with me. I’m doing this one old school as you can tell. This is from Thor S on the monoids video “Why Monoids Kick Monads’ Butt,” that’s what I called the video. Here’s the comment, “Is this aimed at five-year-olds? That is the most long-winded and painful explanation of associative I can imagine.”

    “It feels like you assume everyone watching is too stupid to understand the basic concept, so you have to go over it seven times with minute variations. I kept waiting for there to be any point to this video, but there’s nothing that couldn’t have been summed up in 40 seconds.”

    I didn’t know what to do about this comment, I was thinking about ignoring it. Then I realized that this is an opportunity. This is an opportunity to talk about why I’m doing this. I talk to a lot of people about functional programming. I get various reactions, there are people at various levels of programming skill, functional programming skill.

    There’s all sorts of people out there. One thing I’ve noticed is that there are a lot of concepts that people aren’t familiar with. Sometimes they’re familiar with them, but sometimes they’re not. In recent episodes, I have been making a list of things that I happen to know that I have encountered people who don’t know.

    Not only sometimes do they not know them, but sometimes they are even averse to learning about them. Mostly it seems because they don’t know why it’s important. Especially this kind of stuff like associativity and monoids, they assume that it’s a bunch of math mumbo jumbo. That they don’t need to learn it. That’s how I see [laughs] what they’re explaining.

    That it’s not important. It’s unfortunate because when you dig down deeper they say, “Well, I hear a lot of people talking about them but no one can ever explain why I would need to learn them.” I wanted to correct that. I want to explain it to people in a basic way.

    In a way that makes it not only understandable to them, but also in a way that explains why they would want to use it. Would want to apply this concept. Unfortunately, a lot of people had bad experiences in high school when they learned about associativity. They got very confused with commutativity, and it wasn’t put in any kind of context.

    There is smart people, they might have learned it well enough for the test but they happen to never think about it again. It never came up, or they never thought it came up again. They didn’t have enough context for it. That’s what I’ve done, I’ve made a big list of things. Concepts and ideas, and I tried to put context around them.

    I try to apply the teaching techniques that I know. One of the teaching techniques is make things concrete, and another one is repetition. You’re not going to get everybody with one single explanation. 20 percent of the people might get it on the first explanation, another 20 percent get the second one.

    You repeat stuff in different ways so that it picks up people. Plus, repetition is a way we learn. You hear it once, maybe you remember it. Twice, OK it’s more likely you remember it. The third time, you’re picking up different nuances in it. Now it clicks and you have a more solid understanding, and it’s going to stick in your memory.

    These are basic teaching techniques. They’re human, they’re the way humans learn. To answer the question, am I explaining this for a five-year-old? No, I’m explaining it for people. I’m sorry if you already know this concept and listening to me talk about it is somehow insulting to you or I don’t know, makes you have some pain.

    I’m sorry, just stop the video. You don’t have to watch it, you don’t have to listen. There are a lot of people out there who don’t know these things and I am trying to teach them to them. OK, Mr. Thor S? Could I have said it in 40 seconds? Yes, but not as many people would have remembered it. This is the case.

    There’s billions of YouTube videos out there. If you don’t like my video, that’s fine. I don’t care, that’s fine. There’s a lot of people out there who do like my videos. There’s a lot of videos for you to watch that you will like. Chill out, dude. That’s OK, you don’t have to comment about it. You can just stop it.

    Be nice. It’s fine if you don’t like my video, even if you say something like you don’t like it or you make a critique out of it. That’s fine, but don’t insult my audience about it. The ones who do like it. Thor S, be nice. Thanks everybody for listening. I hope I explained well why I’m doing what I’m doing.

    I explained how I do it, but why? A lot of people, most programmers I would say, are exposed to object oriented ideas. Let’s put it close to 100 percent programmers are exposed to object oriented ideas. I want a similar percentage, close to 100 percent to be exposed to functional programming ideas.

    I think they’re applicable no matter what language you learn, or what language you program in at work. I feel like they’re good ideas and they need to get out there. I want to make them accessible, I want them to make them available. I want people to have context around them so that they know why they’re learning them and can apply them in their daily lives.

    That’s what I’m doing. I spend all this time sitting here talking to you about them, sitting in my office talking about them. I pay for a podcast hosting, I pay for transcription. It’s not free, but I feel the ideas need to get out there. If I can make it into a business at the same time, I feel that’s a win-win. There you go.

    All right people, you all rock. You’re awesome, and just have one thing to say, rock on.

    The post Why am I podcasting about functional programming? appeared first on LispCast.

    Permalink

    Journal 2019.19 - Clojure 1.10.1-beta3, JIRA migration

    Clojure 1.10.1-beta3

    We released Clojure 1.10.1-beta3 this week - please give it a whirl. The only change in it is CLJ-2504 which redoes the clojure.main changes to support different error reporting targets. The most important part of that is that you can specify the target via either a clojure.main option or by a Java system property. The latter is useful because you can take control via existing Java system property support in Leiningen or other tools without needing any subsequent changes in Leiningen.

    Specifically, the default behavior for error reporting with Clojure 1.10.1-beta3 is to write the error report and stack trace to a file, but you can also change that to stderr via system property -Dclojure.main.report=stderr if you prefer.

    This change was made in response to feedback on Clojure 1.10.1-beta2, so thanks! We plan to move pretty directly from here towards an RC and release, so give it a try.

    JIRA migration

    I have spent a fair bit of time this week migrating our aging JIRA system to a new cloud hosted instance. That migration is now largely complete from a data perspective - users, projects, issues, attachments, etc have all been migrated. I am still working on many of the finer details (important reports, permission schemes, screen setups) and new processes for filing issues (won’t require an account) and becoming a contributor. Thanks for your patience, it’s been a slog but this will be a big help moving forward.

    Other stuff I enjoyed this week…

    I was very fortunate in the past week to see two bands that I love for the first time. First, I took a jaunt out to Denver to catch Vulfpeck at Red Rocks. It was cold and a bit snowy, but it was a fantastic show, including a bonus set from the Vulfpeck spinoff Fearless Flyers! I really enjoyed the opening set from Cory Henry - funk organ trio and his guesting during Vulfpeck’s set. There were so many great songs in the show but probably the high point for me was Wait for the Moment featuring Antwaun Stanley on vocals and Cory Henry guesting on organ. Beautiful, amazing version.

    And then this week I got to see Tool back home in St. Louis. Great show and enjoyed seeing a couple new tunes from their (finally) upcoming album - end of August it looks like. Both new songs were predominantly in 7 and they had a large 7-pointed star in the set, so maybe a theme. My favorite album is Lateralus so I probably enjoyed Parabola and Schism the most.

    Permalink

    Ep 029: Problem Unknown: Log Lines

    Nate is dropped in the middle of a huge log file and hunts for the source of the errors.

    • We dig into the world of DonutGram.
    • “We are the devops.”
    • Problem: we start seeing errors in the log.
    • “The kind of error that makes total sense to whoever wrote the log line.”
    • (04:10) We want to use Clojure to characterize the errors.
    • Why not grep?
    • Well…we grep out the lines, count them, and we get 10,351 times. Is that a lot? Hard to say. We need more info.
    • (05:50) Developers think it’s safe to ignore, but it’s bugging us.
    • We want to come up with an incident count by user.
    • “Important thing we do a lot in the devops area: we see data and then we have to figure out, what does it mean?”
    • “What story is the data telling us?”
    • The “forensic data” versus “operating data” (Ep 022, 027)
    • “With forensic data, you’re trying to save the state of the imprint of the known universe over time.”
    • (07:40) The log file is the most common forensic data for applications.
    • To characterize, we need to manipulate the data at a higher level than grep.
    • First goal: make the data useful in Clojure
    • Get a nice sample from the production log, put it in a file, load up the REPL, and start writing code to parse it.
    • Need to parse out the data from the log file and turn it into something more structured.
    • (12:05) Let’s make a function, parse-line that separates out all the common elements for each line:
      • timestamp
      • log level
      • thread name
      • package name
      • freeform “message”
    • “A message has arrived from the developers, lovingly bundled in a long log line.”
    • (13:10) We can parse every line generically, but we need to parse the message and make sense of that.
    • “We want to lift up the unstructured data into structured data that we can map, filter, and reduce on.”
    • We will have different kinds of log lines, so we need to detect them and parse appropriately.
    • We want to amplify the map to include the details for the specific kind of log line.
    • We’ll use a kind field to identify which kind of log line it is.
    • There are two steps:
      1. recognize which kind of log line the “message” is for
      2. parse the data out of that message
    • “Maybe we’ll have a podcast that’s constantly about constantly. It’s not just juxt.”
    • (18:45) How do we do this concisely?
    • Let’s use cond:
      • flatten out all the cases
      • code to detect kind (the condition)
      • code to parse the details (the expression)
    • Can use includes? in the condition to detect the kind and re-matches to parse.
    • (20:00) Why not use the regex to detect the kind too?
    • Can avoid writing the regex twice by using a def.
    • It’s less code, but now we’re running the regex twice: once in the condition and once to parse.
    • We can’t capture the result of the test in cond. No “cond-let” macro.
    • We could write our own cond-let macro, but should we?
    • “When you feel like you should write a macro, you should step back and assess your current state of being. Am I tired? Did I have a bad day? Do I really need this?”
    • (24:05) New goals for our problem:
      • one regex literal
      • only run the regex once
      • keep the code that uses the matches close to the regex
    • Similar problem to “routes” for web endpoints: want the route definition next to the code that executes using the data for that route.
    • Feels like an index or table of options.
    • (25:20) Let’s make a “table”. A vector of regex and handler-code pairs.
    • We need a coffee mug: “Clojure. It’s just a list.”
    • The code can be a function literal that takes the matches and returns a map of parsed data.
    • Write a function parse-details which goes through each regex until one matches and then invokes the handler for that one. (See below.)
    • (30:15) Once we have higher-level data, it’s straight forward to filter and group-by to get our user count.
    • Once again, the goal is to take unstructured data and turn it into structured data.
    • “You have just up-leveled unstructured information into a sequence of structured information.”
    • Can slurp, split, map, filter, and then aggregate.
    • (32:10) What happens when we try to open a 10 GB log file?
    • Sounds like a problem for next week.
    • “Fixing production is always a problem for right now.”
    • “If a server falls over and no one outside of the devops team knows about it, did the server really fall over?”
    • “Who watches the watchers?”

    Related episodes:

    Clojure in this episode:

    • #""
    • re-matches
    • case
    • cond
    • if-let
    • ->>
    • slurp
    • filter
    • group-by
    • def
    • constantly
    • juxt
    • clojure.string/
      • includes?
      • split

    Related links:

    Code sample from this episode:

    (ns devops.week-01
      (:require
        [clojure.java.io :as io]
        [clojure.string :as string]
        ))
    
    ;; General parsing
    
    (def general-re #"(\d\d\d\d-\d\d-\d\d)\s+(\d\d:\d\d:\d\d)\s+\|\s+(\S+)\s+\|\s+(\S+)\s+\|\s+(\S+)\s+\|\s(.*)")
    
    (defn parse-line
      [line]
      (when-let [[whole dt tm thread-name level ns message] (re-matches general-re line)]
        {:raw/line whole
         :log/date dt
         :log/time tm
         :log/thread thread-name
         :log/level level
         :log/namespace ns
         :log/message message
         }))
    
    (defn general-parse
      [lines]
      (->> lines
           (map parse-line)
           (filter some?)))
    
    
    ;; Detailed parsing
    
    (def detail-specs
      [[#"transaction failed while updating user ([^:]+): code 357"
        (fn [[_whole user]] {:kind :code-357 :code-357/user user})]
       ])
    
    (defn try-detail-spec
      [message [re fn]]
      (when-some [matches (re-matches re message)]
        (fn matches)))
    
    (defn parse-details
      [entry]
      (let [{:keys [log/message]} entry]
        (if-some [extra (->> detail-specs
                             (map (partial try-detail-spec message))
                             (filter some?)
                             (first))]
          (merge entry extra)
          entry)))
    
    
    ;; Log analysis
    
    (defn lines
      [filename]
      (->> (slurp filename)
           (string/split-lines)))
    
    (defn summarize
      [filename calc]
      (->> (lines filename)
           (general-parse)
           (map parse-details)
           (calc)))
    
    
    ;; data summarizing
    
    (defn code-357-by-user
      [entries]
      (->> entries
           (filter #(= :code-357 (:kind %)))
           (map :code-357/user)
           (frequencies)))
    
    
    (comment
      (summarize "sample.log" code-357-by-user)
      )

    Log file sample:

    2019-05-14 16:48:55 | process-Poster | INFO  | com.donutgram.poster | transaction failed while updating user joe: code 357
    2019-05-14 16:48:56 | process-Poster | INFO  | com.donutgram.poster | transaction failed while updating user sally: code 357
    2019-05-14 16:48:57 | process-Poster | INFO  | com.donutgram.poster | transaction failed while updating user joe: code 357

    Permalink

    Meta Reduce, Volume 2019.2

    About a month has passed since the last “Meta Reduce” update I wrote. I’ve kept pretty busy in the mean time and now I have quite a few things to share with you. Where do I begin…

    Clojure

    CIDER

    I’ve spent a lot of time lately triaging issues and cleaning up the issue tracker a bit. In the course of this process I’ve identified quite a few good first issues and I’ve labeled them accordingly. I hope that some of you would take a stab at fixing some of them. I keep dreaming of a backlog of less than 50 tickets, but CIDER won’t get there without your help.

    I’ve addressed a few small tickets and I’ve implemented a prototype of find references based on ideas I published earlier. The prototype is rough around the edges, but works reasonably well overall. You can try it with M-x cider-xref-fn-refs.

    I’ve also played a bit with the Read the Docs setup for CIDER and it now serves the documentation for the latest stable version by default.1

    Hard CIDER

    I’ve started a new blog series dedicated to CIDER. Here are the articles in the series so far:

    I plan to write many more, but knowing me - I’ll probably fail miserably. Time will tell.

    Alongside it, I’ve created a demo project I’ll be using the blog series. It’s basically empty at this point, but I hope it’s going to become an useful reference for newcomers at some point.

    I’ve been experimenting with some gif-recording tools around “Hard CIDER” and I really liked Gifox. I’m thinking I might be able to combine it somehow with keycastr for optimal results. As you can see I’d rather have short animated gifs in the posts instead of real videos, but I might reconsider this down the road.

    nREPL

    It has been mostly quiet on the nREPL front lately. I might end up cutting nREPL 0.7 soon just to get the EDN transport out there and postpone the other planned features for a later release. There are a few interesting open tickets that are worth taking a look at and could benefit from some help:

    I’m especially interested in getting nREPL middleware and client authors involved in those conversations.

    Orchard

    Work towards the 0.5 release is progressing steadily. Orchard 0.5.0-beta3 has been out for a while and it seems to be working pretty well.

    The planned ClojureScript support might land in 0.5 as well after all. That’s going to be decided soon.

    Ruby

    Ruby Style Guide

    After a long period of stagnation I’ve started to slowly go through the issues in the backlog and tweaked the guide here and there. My ultimate goal is to go over all the issues and all the existing rules. I want to make sure that the wording and examples everywhere are clear and consistent. More importantly - I want to make sure that the guide is very cohesive and all its parts fit together like a glove.

    I also plan to convert the guide to use AsciiDoc, so we can get richer formatting options and publish it easily in more formats.

    Rails Style Guide

    Andy Waite and Phil Pirozhkov joined the editors team and they have been doing some awesome work cleaning up the guide and updating it.

    RSpec Style Guide

    Phil converted the guide to AsciiDoc recently and the result is really awesome. I’m super excited about converting the Ruby and Rails guides to AsciiDoc as well.

    Special thanks to Dan Allen for his work on AsciiDoctor, (kramdown-asciidoc)[https://github.com/asciidoctor/kramdown-asciidoc], and the valuable tips he gave us during the conversion process!

    Weird Ruby

    I’ve started a new blog series dedicated to some weird and fun aspects of Ruby. Here are the articles in the series so far:

    I’ll be aiming to write 2-3 each month. Fingers crossed.

    RuboCop

    RuboCop got two new major releases recently - 0.68 and 0.69. The first one was notable for the extraction of performance cops to a separate gem (rubocop-performance) and the second one’s big highlight is dropping support for Ruby 2.2.

    I’ve also played a bit with the Read the Docs setup for RuboCop and it now serves the documentation for the latest stable version by default. In addition I’ve added the documentation for rubocop-rails and rubocop-performance under the main project’s documentation:

    I think the result is reasonably good. I hope we’ll soon get some documentation for rubocop-rspec as well.

    Apart from that - we’re steadily moving toward the dream 1.0 release and a new era of stability. I think it might happen before the end of the year. The extraction of the Rails cops into a separate gem is the biggest outstanding task.

    Balkan Ruby

    Balkan Ruby is right around the corner and I’m excited about it. It’s really awesome to host such a great event in my humble hometown of Sofia. Say “Hi!” if you happen to see me there!

    Emacs

    I’ve had very little time for Emacs-related work lately (outside the scope of CIDER and friends).

    Still, I found a bit of time to go through some Solarized and Zenburn related tickets and cut the first new release of Solarized for Emacs in several years.

    In other news I’ve developed a massive time saver when it comes to blogging with Jekyll:

    
    (defun jekyll-insert-post-url ()
      (interactive)
      (let* ((files (remove "." (mapcar #'file-name-sans-extension (directory-files "."))))
             (selected-file (completing-read "Select article: " files nil t)))
        (insert (format "{%% post_url %s %%}" selected-file))))
    
    

    Linking to other articles has never been easier. Got to love Emacs!

    Misc

    All of my major projects (RuboCop, CIDER, Prelude and Projectile) now use stalebot to auto-manage stale issues and pull requests. The current policy is the following:

    • Issues/PRs are marked as stale after 90 days of no activity.
    • They are closed after extra 30 days of no activity.
    • Issues marked as “High Priority”, “Pinned”, “Good First Issue” are never auto-closed.
    • Issues with milestones are never auto-closed.

    This setup might not be perfect, but I think it makes sense for open-source projects. We’re using this same bot to great effect on my day job, but there the settings are way more aggressive. I wrote more about the subject here.

    In the Real World

    I’ve finished reading “Persepolis Rising” and I’d rate it 3/5. “The Expanse” is certainly a bloated soap opera at this point. I’ll need a lot of willpower to convince myself to make it to the next book in the series.

    I’ve decided to recover from it with some sci-fi short stories by Philip Dick and William Gibson. Philip Dick was so ahead of his time! I’ve always loved his work!

    On the cinema/TV front my life has been dominated by “Game of Thrones”. Like many of you I’ve been outraged by how dark #8.3 was and how the story developed throughout the season in general. I’m fairly certain that whatever ending George Martin has in store for us is going to be significantly better. I just hope I’ll live to read it.

    And don’t even get me started on “Endgame”. It was OK, but it felt like some final victory lap, not like a real movie. Not to mention it was utterly predictable (unlike GoT). Still, it was an interesting milestone for the Marvel cinematic universe and I’m curious where does it go from here.

    On the side I’ve finished watching the second season of “Star Trek: Discovery” and it was total garbage. They really need some better writers there, as the main story was both pretty dumb and pretty boring. As was everything else in this season…

    “The Umbrella Academy” was the next show I’ve started. So far I mostly like it, although it’s definitely a bit too slow paced for my taste. I love the characters, though, and the general vibe of the show. Reminds me a bit of classics like “Kick-ass”.

    Time to wrap it up! Until next time!

    1. It used to point to master’s documentation by default. 

    Permalink

    Copyright © 2009, Planet Clojure. No rights reserved.
    Planet Clojure is maintained by Baishamapayan Ghose.
    Clojure and the Clojure logo are Copyright © 2008-2009, Rich Hickey.
    Theme by Brajeshwar.