Friday, June 14, 2019

The Scenic Route To Go Interfaces

Go is an awesome language and interfaces are one of its most powerful features. They allow for decoupling pieces of code cleanly to help make components like database implementations interchangeable. They're the primary mechanism for dependency injection without requiring a DI framework.

Newcomers are often mystified by them but I think they're less confusing if you get to them via the scenic route. Let's look at creating our own types in Go. Along the way we'll find parallels that help make interfaces more clear.

Sidebar: Java Interfaces

If you're not experienced with Java, move on to the next section. Nothing to see here.

If you're experienced with Java, Go interfaces will be pretty familiar and comfortable. The key difference is that a class in Java must explicitly implement a predefined interface. In Go, any type that has the proper method signatures implements an interface, even interfaces created after the type. In Go, implementing an interface is implicit.

Custom Primitive Types

Go emphasizes simple, clear types. You can define your own to help model your problem space. Here I might want to capture a set of boolean flags in one variable:

type BitFlags int32
  • I'm defining my own type
  • I'm giving it the name BitFlags
  • It represents an int32
Why not just use int32 if that's what I want?

One reason is methods. I can attach methods to a type I've defined to give my type additional behavior. Perhaps I define a bunch of constants to represent individual flags and I provide methods like IsSet(BitFlag) bool and Set(BitFlag).

Another reason is explicit type conversion. In other languages it's valid to assign a 64 bit integer variable to a 32 bit integer variable. They're both integers so it's logical to do so. However, you're possibly losing the high 32 bits of the source value. There's an implicit type conversion happening that is often silent and often surprising.

Go doesn't allow implicit type conversions:

i32 := int32(17)
var bf BitFlags
bf = i32 // not allowed
bf = BitFlags(i32) // just fine

This is done to eliminate surprises. The compiler isn't silently setting a type conversion that can change your data without your knowledge. It requires that you state that you want the conversion. This makes it harder for users of the BitFlags type to accidentally provide a numeric value that shouldn't be interpreted as flags.

Custom Struct Types

type Foo struct {
A string
B int
}
  • I'm defining my own type
  • I'm giving it the name Foo
  • It contains the following data
Structs allow you to bundle pieces of data together into a single item. That item can be passed around as a unit. Like custom primitive types, custom struct types can have methods attached.

Also like custom primitive types you can assign one to the other if they are equivalent using an explicit type cast:

type Foo struct {
A string
B int
}

type Bar struct {
A string
B int
}

func main() {
f := Foo{A: "foo", B: 3}
var b Bar
b = f // invalid
b = Bar(f) // just fine
}

Custom Interface Types

An interface type specifies requirements for behavior. Methods are behavior which is why we tend to name them with verbs or action phrases. In go, any type that has those exact method signatures satisfies the interface's requirements.

type Storage interface {
Create(key string, o Object) error
Read(key string) (Object, error)
Update(key string, o Object) error
Delete(key string) error
}

  • I'm defining my own type
  • I'm giving it the name Storage
  • Anything with these methods qualifies as this type

Using interfaces I can define requirements for a storage system for my application to use. My application needs something through which I can create, read, update, and delete objects associated with a given key.

func GeneratePDFReport(output io.Writer, storage Storage) error {
// ...
}

My application isn't concerned with how those operations are actually performed. The underlying storage could be an SQL database, S3 bucket, local files, Mongo, Redis, or anything that can be adapted to do those four things. Perhaps the report generator supports many storage mechanisms and when the application starts it decides which storage to use based on a config file or flags. It also means that when I need to write tests for my report generator I don't need to have an actual SQL database or write files to disk; I can create an implementation of Storage that only works with test data and behaves in an entirely predictable way.

Interface nil and Type Assertions

For all variables of interface types the runtime keeps track of two things: the underlying value and that value's type. This leads to two different ways an interface variable can be nil. First, the interface value itself can be nil. In this case there's no type information, no underlying value; nothing to talk about. This is very common with the error interface. In the case of no error the interface variable itself is nil because there's no error to be communicated.

In the second case there's type information but the underlying value is nil. A comparison like myInt == nil returns false because the interface value exists and points to type information. Ideally in this case nil is useful for that type as in the final example in Dave Cheney's zero post.

If needed you can get at the underlying value inside an interface variable.

io.Writer is a commonly-used interface. It has only one method: Write([]byte) (int, error). If I have a variable out of type io.Writer the only operation I can perform on it is Write. What if I want to Close it? Ideally if you have Close as an requirement you should make Close part of the interface type of your variable (or use io.WriteCloser instead of io.Writer).

For the purposes of illustration you can do a type assertion. This asks that the runtime verify that the underlying thing in your variable is of a certain type:

if c, ok := out.(io.WriteCloser); ok {
err := c.Close()
// handle error
} else {
// not an io.WriteCloser!
}

In the above example, if out happens to be an io.WriteCloser then ok will be true and c will be out as type io.WriteCloser. If out doesn't happen to be an io.WriteCloser, ok is false and c is zero for io.WriteCloser which is nil.

Anonymous Struct Types

Given a preexisting struct type I can create a value of that with data in one statement:

f := Foo{
A: "foo",
B: 17,
}
  • I'm creating a variable f
  • It is of type Foo
  • It contains these values

In the above struct examples each of the types I defined have a name; this isn't always necessary providing I'm creating the struct on the spot and assigning it somewhere.

ff := struct {
A string
B int
}{
A: "foo",
B: 17,
}



  • I'm creating a variable ff

  • It is of this type

  • It contains these values

  • Like the named struct types above I can do an assignment with an explicit type conversion:

    f = ff // invalid
    f = Foo(ff) // totally fine

    This sort of anonymous struct type is common in table driven tests. It's also not uncommon in defining nested structs as mholt's JSON to Go converter does.

    You'll also sometimes see this:

    stringSet := map[string]struct{}{}



  • I'm creating a variable stringSet

  • It is of this type

  • It contains these values

  • The last part looks a little strange. It's a map with strings for keys but what are the values? The values are empty structs: they contain nothing and therefore take up no memory. What good is that? It's a map that only tracks the presence of keys which functions as a logical set. The final magenta curly braces define the initial contents of the map; it's empty.

    Anonymous Interfaces

    Just like you can have anonymous struct types you can have anonymous interface types. The following are equivalent:

    var foo io.Reader

    var foo interface{
    Read([]byte) (int, error)
    }

    In either case I can assign anything with a Read([]byte) (int, error) method to foo.

    We're near the end of our journey which brings us to the enigmatic interface{}:

    foo := interface{}{}
    var foo interface{} = nil
    • I'm defining a variable
    • I'm giving it the name foo
    • Anything with these methods qualifies as this type
    • The contents are explicitly zero
    interface{} is an anonymous interface type. It has no requirements so any value is suitable. I can pass around a value of type interface{} but I can't do anything with it without using a type assertion or the reflect package.

    In this way the empty interface is different than other interface types in that it doesn't specify required behavior. It sidesteps the type system and turns what could be compile-time errors into run-time errors. When writing code that uses the empty interface use great care.

    Saturday, June 8, 2019

    Wednesday, March 6, 2019

    3D Printer Filament Cheap Vacuum Desiccation

    Lots of 3D printer filaments are hygroscopic; they absorb water from the atmosphere. This is problematic for filaments because during printing they are subject to a sudden, dramatic jump in temperature. This causes the absorbed water to explode potentially making detrimental changes to the material properties of the filament. For filaments like PETG and Nylon this makes for a rougher surface and anomalies in the finished part.

    The standard mechanism for drying filament is to put it in the oven for a while. I started getting strange results with my PETG because I got lazy and left it out between prints. I wanted to fix the issue but wasn't keen about putting plastics in my oven. I wondered about other methods and the silica gel packets came to mind. Those are the little packets that come with shoes and other things and say DO NOT EAT. I was skeptical about the power of these to reverse the absorption that had already taken place, at least not in a timely fashion. Also, I like to tinker and wanted to experiment with weird ideas.

    I like to sous vide and had recently gotten a food vacuum sealer with an attachment for sucking the air out of wine bottles. I could seal the filament spools in a vacuum bag with some desiccant but I felt like it would work better if I could get closer to a proper vacuum. I didn't want to invest in a high-grade vacuum chamber and pump for an experiment. I started looking around and found the FoodSaver Quick Marinator; a rigid container with a valve for the hose that connected the vacuum sealer to attachments.

    Here's my setup:

    • Off-brand food vacuum sealer
    • The FoodSaver Quick Marinator
    • The vacuum sealer attachment hose
    • Coffee Filters
    • Flower desiccant powder
    The flower desiccant powder is the same material as the silica gel packets you can get but it's very fine, providing a higher surface area, and they're trivial to renew.

    I put 5 (arbitrary) tablespoons of desiccant powder into a coffee filter, fold it up, and tape it closed so the powder can't easily escape.

    I put my filament spool into the marinator, drop my desiccant pack on top and put the lid on the with tabs on the same side.

    Then, suck the air out. I run the pump until it shuts off. Wait a few seconds, and repeat. I go through about three cycles of this. After the pump has shut off, ensure the pump isn't warm; you don't want to ruin your pump by overheating it.

    Finally I ensure the valve is closed before removing the hose.

    Surprisingly, this worked. My next round of PETG printing produced a nice, smooth finish. I've taken to storing the spools with one of my homemade desiccant packets in a small Space Saver bag and vacuum the air out. It doesn't look like they sell just the small bags and the medium and larger are grossly too large for a filament spool.

    It's important to note that so far I've only done this desiccating process on Dremel filament spools which are 0.5kg. As you can see in the picture above these spools just fit in the quick marinator. Larger spools probably won't fit, loose filament can depending on how much you want to stuff in there. When I get larger spools I'll probably have to come up with another mechanism.

    Thursday, January 10, 2019

    New 3D Printer: Dremel 3D45

    Background

    For a while I had a 3D printer. Specifically a Printrbot Simple Metal. When it worked, it was a lot of fun. I could start with an idea and turn it into a real physical object without the need for a big workshop, huge mess, or personal injury. When it worked, it was great! But the process of getting it to work and keeping it working took all the joy out of the prospect of desktop fabrication.

    Three years later I found myself thinking about getting another 3D printer, hoping that the reliability had improved. I started looking at what was on the market with an emphasis on reliability: I didn't want to tinker with the printer, I just wanted to turn designs into objects. Dremel seemed to have some reliable offerings though a little pricey. I pulled the trigger on the 3D45 and so far I'm very pleased.

    Disclaimer

    I should be getting compensated for this review. My printer came with a small flyer that said if I post a review of the printer on my blog I would receive two spools of PLA for free. The flyer didn't provide direction on what the review should say.

    Initial Impressions

    I pulled the printer out of the box and had it set up in about 20 minutes. I was able to start a job for a frog figure from the internal storage. It came out almost perfect with a couple of misaligned layers and some poor adhesion ("springing") at the top.

    Overall though I had good luck with the printer initially. I would sometimes have trouble with first layer adhesion which is the most common problem printing. Trying different things to correct it I found that washing the build plate, perform bed leveling, and reapplying glue would almost always correct the issue.

    The Good, the Bad, and the Ugly

    Pros

    It just works. I've used an entire spool of ECO-ABS with an overwhelmingly positive success to failure ratio. I've printed in PETG and Nylon as well with no failures there. Nylon is notoriously annoying to print with and it worked on my first try. Note; The ECO-ABS is a proprietary formulation of PLA.

    It looks nice. While I don't actually care how it looks, the fact that it's fully self-contained means that the cat, the dog, dust, Dorito crumbs, ejected shell casings, etc won't put my prints a risk.

    It has almost all the bells and whistles. As stated, it's fully-enclosed to help keep it work inside and reduce warping. It has a heated bed to help with the same. The build plate is tempered glass in an easy to remove and handle frame. I'm really pleased with how easy it is to work with the build plate. It's got a touch screen that provides helpful information and allows you to perform basic operations and start/pause/cancel prints from onboard storage or a USB drive. It prints "ECO-ABS", PLA, PETG, and Nylon. It has an integrated web cam, runout sensor, yadda-yadda-yadda.

    The filament seems really good. The color and performance are consistent throughout the spool. I don't really have problems with first-layer or intra-layer adhesion. The Dremel filament spools have an embedded RFID tag that the printer reads and use to automatically set the temperature for the extruder and bed.

    Cons

    It's expensive. The printer retails for $1800. I got mine for $1400 on Amazon black Friday. That's fine as a reliable device that lasts years is worth investing in. The filament, however, is also expensive. Common, non-funky filaments run between $15-30 per kg. Dremel filament runs $30-40 per .5kg. This is tempered somewhat by the fact that between the quality of the printer and their filament my prints succeed more so I waste less (save my own design mistakes).

    It's something of a walled garden. There's not a lot of room to tinker with the printer. I don't want to tinker with the printer... I don't want to have to. However, should it be necessary it would be nice. I can't complain too much as I made a conscious decision to sacrifice hackability for reliability.

    The network features are weak. You can manage and monitor print jobs through their cloud service but you can't do so over the LAN. The frame rate for the video in their cloud service is like 0.1fps. It's difficult to actually understand what's happening.

    WTF?

    The cloud slicer is garbage. You can upload an STL to the cloud service and have it do the slicing for you. It was easy! I never got a successful print from the cloud slider. Usually there would be a loss of inter-layer adhesion leaving me with a slinky vaguely in the shape of my print. Using the Dremel Cura desktop slicer works extremely well and when I upload the gcode it produces to the cloud service the results are great. Unfortunately they kind of tout the cloud service and using only the cloud service brought me nothing but failure. Luckily I have some printing experience and knew how to troubleshoot this.

    "Filament DRM". They strongly encourage you to print using only Dremel filament. It seems to be high quality and the RFID identification feature is definitely neat. However, the state that using non-Dremel filament will void your warranty. This seems insane to me. I get that they wouldn't want to support issues people have using janky filament. I would totally accept that if I use 3rd party filament and my house burns down they shouldn't be held liable. But to say that they are unwilling to support me for using less expensive filament leaves a bad taste.

    And the filament cost. I would feel a lot less annoyed about the quasi-restriction of using only Dremel filament if it weren't 2-4 times the cost of other filaments. It is good but the difference in cost is kind of crazy. To some extent they market this product line for education; schools can't afford this. Also, they should encourage other filament vendors to use the same RFID spec. It's a cool feature. Overall, they should make the filament significantly cheaper or penalize their customers less for using third-party filaments.

    I'm really disappointed that I can't really use the built-in camera decently. I get that they wouldn't want to stream 720p over the Internet to everyone; they aren't trying to be a video streaming service. There should be a way for me to watch it in full quality over my home network.

    Trouble Strikes!

    Eventually I had a serious issue that wash/level/glue wouldn't resolve. I wasn't getting a first layer. The extruder would travel around the surface of the bed and yell CLICK CLICK CLICK CLICK as though some gears were slipping, like something was stuck. Thinking I had a clog I looked in the manual for their clearing process. The troubleshooting matrix says that if have a clog to contact support. Simultaneously the manual has instructions for clearing a clog. I went through the clog clearing process and found that the extruder could push filament out fine. Attempting another print I got the same symptom. It seemed that the tip of the nozzle was flush with the bed and it was clogged because there was no place to extrude filament to.

    I call customer support but it was a Friday afternoon. Wonderfully, they have US-based support. Unfortunately for me they're in Central Time, I'm in Pacific Time and they were already closed. I used their email contact form and received an automated response saying I'd hear back from them in two business days. This being Friday I shouldn't hear back from them until Tuesday or Wednesday. On Monday I got a response with corrective instructions that fixed the issue in a few minutes. It sucks that I was "out of commission" for several days. It's awesome that I had access to people whose job it was to help me. Free input on 3D printing issues will vary far and wide on the nature of your problem and remediation.

    Conclusions

    I think my money on this printer was well-spent. I've been really enjoying it. I feel like I got the reliability I was looking for. The filament is really expensive which makes me hesitant to experiment.