Forcing www and https with CloudFlare and NearlyFreeSpeech in one 301 redirect (rather than two)

  1. Put this in .htaccess on NearlyFreeSpeech
    RewriteCond %{HTTP:X-Forwarded-Proto} =http [OR]
    RewriteCond %{HTTP_HOST} !^www\. [NC]
    RewriteRule ^(.*)$ https://www.ericflin.com/$1 [R=301,L]
  1. On NearlyFreeSpeech, turn off canonical URLs and turn off force HTTPS. If either is forced on, requests to http://example.com get redirected to https://example.com and then to https://www.example.com, regardless of settings in .htaccess.
  1. On CloudFlare, add the following page rule:
    💡
    URL: http://*example.com/* Rule: Forwarding URL (Status Code: 301 - Permanent Redirect, Url: https://www.example.com/$2)
  1. Make sure enforce HTTPS is turned on in CloudFlare.
  1. You don't need any other rules because the .htaccess takes care of redirects from https://example.com to https://www.example.com

Installing a parallel port printer on Windows 10 with a USB-to-LPT port converter

These instructions assume that the $6000 printer you bought seven years ago with only a parallel port interface is still supported by the manufacturer and has drivers for Windows 10.

In Control Panel, go to Devices and Printers. Viewing by large icons instead of categories might be helpful for finding the Devices and Printers button.

Click Add a printer. Then click The printer I want isn't listed. Select Add a local printer or network printer with manual settings. Click Next.

Select Use an existing port and choose USB002 (or USB003, or USB004, or...) (Virtual printer port for USB).

To determine whether to use USB003, USB002, or USB001:

  • find USB2.0-Print (or similar) in the Devices and Printers (Control Panel) window
  • right click and select Properties
  • choose the Hardware tab
  • select USB Printing Support
  • click Properties
  • go to the Details tab
  • under Property, select Children from the dropdown menu.
  • It should say USB003 or USB002 at the end of the line under Value

Click Next. Select your printer's driver and finish setup.

Using SEMPRE (Semantic Parser with Execution) as a Java library

Getting started

  • Step 1: Get the SEMPRE repository:
    git clone https://github.com/percyliang/sempre
  • Step 2: Navigate to the SEMPRE repository and follow the steps listed under Easy Setup in README.md
  • Step 3: Follow the steps listed below

You'll need

  • libsempre/sempre-core.jar
  • libsempre/sempre-cache.jar
  • libsempre/sempre-corenlp.jar if using CoreNLPAnalyzer
These are produced by following the steps under Easy setup in the README.md file of the SEMPRE repository.

You'll also need many of the dependencies under lib

Make sure the jars are included somehow when compiling your project. I like using gradle, so I just threw all the jars in a directory and included the directory as a dependency for my project:

dependencies {
    implementation fileTree(dir: '../lib', include: '*.jar')
}

Write yourself a parser class

The hot thing to do here would be to force you to dig through a bunch of poorly written explanations to find the full code listing. It's usually buried in the middle somewhere, disguised to look like another partial chunk of code. I'm not a fan of that. Here's the full code listing for a sample Parser class. We'll explain things later:

Parser.java

import edu.stanford.nlp.sempre.*;
import edu.stanford.nlp.sempre.corenlp.CoreNLPAnalyzer;
import fig.basic.Pair;

import java.util.*;
import java.util.stream.Collectors;

public class Parser {
    private Builder builder;
    private Dataset dataset;
    private Grammar grammar;
    private LanguageAnalyzer analyzer;

    Parser(LanguageAnalyzer analyzer) {
        this.builder = new Builder();
        this.dataset = new Dataset();
        this.grammar = new Grammar();
        this.analyzer = analyzer;

        // Equivalent command line option: -languageAnalyzer corenlp.CoreNLPAnalyzer
        // if `this.analyzer` is `new CoreNLPAnalyzer()`
        LanguageAnalyzer.setSingleton(this.analyzer);

        this.repository = repository;
    }

    public Parser() {
        this(new CoreNLPAnalyzer());
    }

    // Equivalent command line option: -Grammar.inPaths [grammarPath]
    public void setGrammarPath(String grammarPath) {
        grammar.read(grammarPath);
        builder.grammar = grammar;
    }

    // Equivalent command line option: -Dataset.inPaths train:[examplePath]
    public void setExamplePath(String examplePath) {
        dataset.readFromPathPairs(Collections.singletonList(new Pair<>("train", examplePath)));
    }

    public void initialize() {
        builder.buildUnspecified();
    }

    public void learn() {
        // Equivalent command line option: -FeatureExtractor.featureDomains rule
        FeatureExtractor.Options o = new FeatureExtractor.Options();
        o.featureDomains = Collections.singleton("rule");
        FeatureExtractor.opts = o;
        FeatureExtractor f = new FeatureExtractor(builder.executor);

        // Equivalent command line option: -Learner.maxTrainIters 3
        Learner.opts.maxTrainIters = 3;
        Learner learner = new Learner(builder.parser, builder.params, dataset);
        learner.learn();
    }

    // Parse with SEMPRE
    public Response parse(String query) {
        Example.Builder b = new Example.Builder();
        b.setId("session:1");
        b.setUtterance(query);
        Example ex = b.createExample();
        Response response = new Response(builder);

        ex.preprocess();

        // Parse!
        builder.parser.parse(builder.params, ex, false);
        response.ex = ex;
        response.candidateIndex = 0;

        return response;
    }
}

If you're using the sample class above, you'll also want to copy the Response class from within edu.stanford.nlp.sempre.Master. It's reproduced here:
Response.java

import edu.stanford.nlp.sempre.Builder;
import edu.stanford.nlp.sempre.Derivation;
import edu.stanford.nlp.sempre.Example;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

// Copied from edu.stanford.nlp.sempre.Master
public class Response {
    // Example that was parsed, if any.
    public Example ex;
    private Builder builder;

    // Which derivation we're selecting to show
    int candidateIndex = -1;

    // Detailed information
    public Map stats = new LinkedHashMap<>();
    public List lines = new ArrayList<>();

    public String getFormulaAnswer() {
        if (ex.getPredDerivations().size() == 0)
            return "(no answer)";
        else if (candidateIndex == -1)
            return "(not selected)";
        else {
            Derivation deriv = getDerivation();
            return deriv.getFormula() + " => " + deriv.getValue();
        }
    }
    public String getAnswer() {
        if (ex.getPredDerivations().size() == 0)
            return "(no answer)";
        else if (candidateIndex == -1)
            return "(not selected)";
        else {
            Derivation deriv = getDerivation();
            deriv.ensureExecuted(builder.executor, ex.context);
            return deriv.getValue().toString();
        }
    }
    public List getLines() { return lines; }
    public Example getExample() { return ex; }
    public int getCandidateIndex() { return candidateIndex; }

    public Derivation getDerivation() {
        return ex.getPredDerivations().get(candidateIndex);
    }

    public Response(Builder b) {
        this.builder = b;
    }
}

Usage

ParserTest.java

import edu.stanford.nlp.sempre.SimpleAnalyzer;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

public class ParserTest {
    @Test
    public void testParser() {
        // We can use SimpleAnalyzer instead of CoreNLPAnalyzer (default when you run
        // the `run` script in SEMPRE is SimpleAnalyzer; default for the sample class above
        // is CoreNLPAnalyzer)
        Parser parser = new Parser(new SimpleAnalyzer());

        // Load grammar
        parser.setGrammarPath("arithmetic-tutorial.grammar");

        // Load training examples
        parser.setExamplePath("arithmetic-tutorial.examples");

        // Must call initialize before learning or parsing
        parser.initialize();

        // Learn from training examples
        parser.learn();

        // Unambiguous query (two plus four means 2 + 4, which is 6, and we expect only 1 prediction)
        Response resp = parser.parse("two plus four");
        assertEquals("(number 6)", resp.getAnswer());
        assertEquals(1, resp.ex.getPredDerivations().size());

        // Ambiguous parse (two and five could mean 2 + 5 or 2 * 5, so we expect 2 predictions)
        Response resp = parser.parse("two and five");
        assertEquals(2, resp.ex.getPredDerivations().size());
    }
}

Frequentist statistics

Frequentist probability asserts that the “probability” of something is the frequency at which it’s observed. Frequentist statistics extends the concept to statistics.

So replace every instance of the words chance and probability with the word frequency (adjusting the sentence as necessary to make logical sense) and you’re golden. 1This advice assumes you know you’re characterizing your data, not your hypothesis.

Alternatively, because this adjustment tends to lead to stilted sentences, replace x% chance or 0.x probability with x% of

Examples (replacing chance/probability with frequency):

  • Misleading: The probability that this 95% confidence interval contains the test statistic is 95%.
  • Accurate: The frequency with which 95% confidence intervals (in a number of repeated trials) contain the test statistic is 95%
  • Misleading: The p-value for this test was 0.03, so the probability of obtaining the data observed, assuming there was no difference between treatments, was 0.03.
  • Accurate: The p-value for this test was 0.03, so the frequency with which one might obtain the data observed (in a number of repeated trials), assuming there was no difference between treatments, was 3%.

Examples (replacing x% chance or 0.x probability with x% of):

  • Misleading: There is a 95% chance that this 95% confidence interval contains the test statistic.
  • Accurate: 95% of 95% confidence intervals contain the test statistic.
  • Misleading: There was a 3% chance (p = 0.03) of seeing these observations, assuming there was no difference between treatments.
  • Accurate: 3% of repeated trials (p = 0.03) would see similar observations, assuming there was no difference between treatments.


1Frequentist statistics only helps you draw conclusions on your data, not your hypothesis. It makes more sense to talk about the frequency of observing some data than the frequency of observing some hypothesis. If you fit your statements into the template

We'd expect [frequency] of repeated trials to have [data], assuming [hypothesis],
your new statement is probably accurate. Swapping data and hypotheses leads to incorrect/misleading statements.

Examples (statements that make it sound like you're testing your hypothesis (misleading) vs. ones that sound like you're testing data (correct)):

  • Misleading (characterizing the hypothesis): There was a 3% chance (p = 0.03) that there was no difference between treatments (because we observed these data).
    • Rephrased (still misleading; data and hypothesis are swapped): We'd expect 3% of repeated trials (p = 0.03) to have no difference between treatments, assuming the data were as observed.
    • Rephrased (accurate): We'd expect 3% of repeated trials (p = 0.03) to have these data, assuming there was no difference between treatments.
  • Accurate (characterizing the data): 3% of repeated trials would observe these data, assuming there was no difference between treatments.
    • Rephrased (still accurate): We'd expect 3% of repeated trials to have these data, assuming there was no difference between treatments.

Spring Boot/JPA/Hibernate annotations in Kotlin data classes

I’m still learning Kotlin, so I didn’t know this, but Kotlin has annotation use-site targets, like @get, @set, and @field.

I wanted to add timestamps via Hibernate, and they weren’t working. When I was trying to add a @(org.hibernate.annotations.)CreationTimestamp annotation and various other annotations without use-site targets, none of them were working. Couldn’t figure out why. Turns out, when you’re making a data class, you have to specify whether those annotations apply to getters, setters, fields, constructor parameters, etc.

A Kotlin data class that looks like this:

package com.example.test

import javax.persistence.*
import java.util.UUID
import org.hibernate.annotations.CreationTimestamp
import org.hibernate.annotations.UpdateTimestamp

@Entity
data class UselessThing(
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    val id: UUID?,
    
    val name: String = "",
    
    @CreationTimestamp
    @Column(updatable = false)
    val createdDate: Date = Date(),
    
    @UpdateTimestamp
    val modifiedDate: Date = Date()
)

Would probably translate to something like this in Java:
// imports omitted

public class UselessThing {
  private UUID id;
  private String name;
  private Date createdDate = Date();
  private Date modifiedDate = Date();  

  // This is a constructor.
  public UselessThing(
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        UUID id,

        String name,

        @CreationTimestamp
        @Column(updatable = false)
        Date createdDate,

        @UpdateTimestamp Date modifiedDate) {
    this.id = id;
    this.name = name;
    this.createdDate = createdDate;
    this.modifiedDate = modifiedDate;
  }

  /* getters, setters, copy methods and whatever else
  Kotlin data classes make */
}

Instead, You’d create a Kotlin data class that looks like this:

package com.example.test

import javax.persistence.*
import java.util.UUID
import org.hibernate.annotations.CreationTimestamp
import org.hibernate.annotations.UpdateTimestamp

@Entity
data class UselessThing(
    @field: Id
    @field: GeneratedValue(strategy = GenerationType.AUTO)
    val id: UUID?,
    
    val name: String = "",
    
    @field: CreationTimestamp
    @field: Column(updatable = false)
    val createdDate: Date = Date(),
    
    @field: UpdateTimestamp
    val modifiedDate: Date = Date()
)

The latter, I’m guessing, probably translates to something roughly like this in Java:

// imports omitted

public class UselessThing {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private UUID id;
  private String name;

  @CreationTimestamp
  @Column(updatable = false)
  private Date createdDate = Date();

  @UpdateTimestamp
  private Date modifiedDate = Date();  

  public UselessThing(UUID id, String name, Date createdDate, Date modifiedDate) {
    this.id = id;
    this.name = name;
    this.createdDate = createdDate;
    this.modifiedDate = modifiedDate;
  }

  /* getters, setters, copy methods and whatever else
  Kotlin data classes make */
}

The latter, unlike the former, also works.

I encountered another gotcha with timestamps. When the client tried updating a UselessThing (via the .copy() method to create a new object with updated properties and JpaRepository’s save() method to persist the object) and didn’t specify a createdDate, the created_date column in the database would get updated every time the entity was updated.

Or something like that. Going over that again now, there must have been something else going on.

Anyway, adding the @Column(updatable = false) annotation fixed that issue.

Bash scripting if statements. Also, running flow in a docker container for Visual Studio Code's flowtype.flow-for-vscode extension.

One of the more annoying things I encountered while learning bash scripting was the examples people used for if statements. Most of the tutorials and examples I followed used if statements with square brackets. Later, I learned that square brackets are only sometimes (or almost never) necessary.

Square brackets are just a shorthand form of the test command, and if just evaluates whether the expression/command that follows it returns a 0.

But commands' return values aren't displayed anywhere in plain sight, so then I learned about the special variable $?, which stores the return value of the previous command.

Basically, if you're using grep to determine whether or not something exists, wrapping it with the test command is unnecessary. grep already returns a 0 when it finds something, and it returns something else when it doesn't. Negating the return value of a grep command only requires that you prepend an exclamation point to the whole thing.

See example below for what I'm talking about. No square brackets anywhere in the if statements, because all I'm testing for is presence or absence of certain strings. Oh, and I wrote the script so Visual Studio Code could use flow from a docker container.

It was balking at my usual script containing just
#!/bin/bash
docker run --rm -v "$PWD":/src -w /src [image]:[version] [command] $*
which I used for lying that I had Golang installed on the system (where image and version were the name and version of a docker image derived from golang:1.9) and [command] was one of the commands Visual Studio Code was complaining about not having (and were contained in the docker image):
    go-outline: go get -u -v github.com/ramya-rao-a/go-outline
    go-symbols: go get -u -v github.com/acroca/go-symbols
    gocode: go get -u -v github.com/nsf/gocode
    godef: go get -u -v github.com/rogpeppe/godef
    godoc: go get -u -v golang.org/x/tools/cmd/godoc
    gogetdoc: go get -u -v github.com/zmb3/gogetdoc
    golint: go get -u -v github.com/golang/lint/golint
    gomodifytags: go get -u -v github.com/fatih/gomodifytags
    gopkgs: go get -u -v github.com/uudashr/gopkgs/cmd/gopkgs
    gorename: go get -u -v golang.org/x/tools/cmd/gorename
    goreturns: go get -u -v sourcegraph.com/sqs/goreturns
    gotests: go get -u -v github.com/cweill/gotests/...
    guru: go get -u -v golang.org/x/tools/cmd/guru
    impl: go get -u -v github.com/josharian/impl
    go play: go get -u -v github.com/haya14busa/goplay/cmd/goplay
There was one more that was not in that list that I also had to install, but I don't remember what it was. I can post the Dockerfile for the image later. Anyway, knowing about $? is also helpful for figuring out how you want to structure commands within if statements.
#!/bin/bash
LOG_FILE=/home/dogi/flow_log

function run_flow {
    echo "Creating flow container" >> $LOG_FILE
    docker run -dti --name "flow_server" -v "$PWD":"$PWD" -w "$PWD" vscode-node:8 bash
}

function start_flow {
    # Don't let crazy process spawners try to spawn flow repeatedly
    if docker ps -a | grep flow_server | grep seconds; then
        echo "Wait a minute" >> $LOG_FILE
        echo "`docker ps -a`" >> $LOG_FILE
        return
    fi

    # Run the flow container if it doesn't exist already.
    if ! docker ps -a | grep flow_server; then
        echo "Flow container doesn't exist; creating" >> $LOG_FILE
        echo "`docker ps -a`" >> $LOG_FILE
        run_flow
    # If the flow container exists, but was started in a different directory
    # (i.e., not found in mounts), destroy it and create it in the current one.
    elif ! echo $(docker inspect -f '{{ .Mounts }}' flow_server) | grep -w "$PWD "; then
        echo "Flow container has wrong mounts" >> $LOG_FILE
        echo "`docker inspect -f '{{ .Mounts }}' flow_server`" >> $LOG_FILE
        if docker ps -a | grep Up | grep flow_server; then
            echo "Flow container is running; killing" >> $LOG_FILE
            echo "`docker ps -a`" >> $LOG_FILE
            docker kill flow_server
        fi
        if docker ps -a | grep flow_server; then
            echo "Flow container exists; deleting" >> $LOG_FILE
            echo "`docker ps -a`" >> $LOG_FILE
            docker rm flow_server
        fi

        run_flow
    # Run the flow container if it already exists and was started in the current
    # directory
    elif ! docker ps -a | grep Up | grep flow_server; then
        echo "Flow container exists in the proper directory but isn't running; restarting container" >> $LOG_FILE
        echo "`docker ps -a`" >> $LOG_FILE
        docker start flow_server
    fi
}

# Suppress docker output. VS Code just wants flow's output.
start_flow > /dev/null

docker exec -i flow_server flow $*

Using Flow with create-react-app

If you're using create-react-app and want to use Flow, follow the instructions at this link: https://flow.org/en/docs/tools/create-react-app/ instead of the ones at this one.