Hacker Newsnew | past | comments | ask | show | jobs | submit | pbnjay's commentslogin

FYI this is not true in the US. Zip codes identify postal routes not locations


I’m building an ETL tool that “just works” and gets out of the way. I can write shell scripts and python to do this stuff but honestly I just want to drop my files/API results into a GUI tool and have it combine things for me. Landing page is at https://eetle.com


Yes I was very excited to see the new json encoding changes land, can’t wait to try them out! The new omitempty and map key marshalling in particular will help clean up some of my ugly code.


Dropping the kids off at the pool is a euphemism for a bodily function that diuretics like coffee help with…


The other one.


Probably gets pretty costly if you’re running a lot of data through it. Now if you could implement a Tailscale DERP server in a lambda that would be pretty amazing: https://tailscale.com/kb/1232/derp-servers


I did toy around with Tailscale initially trying to get it to spin up as an exit node but wasn't able to get that functional. I did manage to get Tailscale Funnel to work as the tunnel mechanism to Lambda, but unfortunately the performance was really poor.


Given the redundancy of multiple locations, I think even cutting power it would be difficult to cause an extended outage.


I have a variety of Shelly 1 devices in my house, one of which is on the oldschool Garage Door opener (with a reed switch for open/close tracking) - Very inexpensive, flexible for other applications, and works with HA etc.

https://us.shelly.com/products/shelly-1-gen3


The Shelly Uni is the correct device to use for these applications:

https://shelly.digital/product/shelly-uni/

It can be powered by 12-24V DC or AC, so you can power it off the accessory wiring of the device. It has two relays to control two different actions (my gate has a secondary partial open mode, to walk through it without opening all the way), and has two digital inputs to monitor state of the device (my gate exposes an "is open" and "is closed" state).

The setup in the app is a bit confusing though. Well the app seems like it was designed by a Unix neckbeard, but it is very powerful - I haven't needed to switch to MQTT/Home Assistant to do the automations I want.

My devices can be triggered by a momentary switch: press and release to open, press and release again to close. The app has a "Momentary" switch type, but that did not work for me. I had to use "Toggle Switch" then set a auto off timer for 0.1 seconds.


Huge fan of shelly. I wrote a little sinatra web-server that can just show the current state, and toggle the state, of a bunch of lights around my yard. I really appreciate that all you need is http, no cloud, no fuss to just put together a custom ui for them. Couldn't recommend them more highly


Isn't it even simpler in Go? No channels necessary, Each goroutine gets an index or slice, and writes the results to a shared array.

All you need is the goroutines to report when done via a WaitGroup.


That doesn't satisfy the "report the results as they become available" requirement.

The desired behavior is that printing the first result should only wait for the first result to be available, not for all the results to be available.


Would be trivial to show the results live though, especially for atomic values: https://go.dev/play/p/PRzzO_skWoJ


That's not "live"; that's just polling.


Modified the above to https://go.dev/play/p/DRXyvRHsuAH You get the first result in results[0] thanks to `atomic.Int32`.

    package main

    import (
     "fmt"
     "math/rand"
     "sync/atomic"
     "time"
    )

    func main() {
     args := []int{5, 2, 4, 1, 8}
     var indexGen atomic.Int32
     indexGen.Store(-1)
     results := make([]int, len(args))
     finished := make(chan bool)

     slowSquare := func(arg int, index int) {
      randomMilliseconds := rand.Intn(1000)
      blockDuration := time.Duration(randomMilliseconds) * time.Millisecond
      fmt.Printf("Squaring %d, Blocking for %d milliseconds...\n", arg, randomMilliseconds)
      <-time.After(blockDuration)
      idx := indexGen.Add(1)
      results[idx] = arg * arg
      fmt.Printf("Squared %d: results[%d]=%d\n", arg, idx, results[idx])
      finished <- true
     }

     prettyPrinter := func() {
      for range time.NewTicker(time.Second).C {
       fmt.Println("Results: ", results)
      }
     }
     go prettyPrinter()
     for idx, x := range args {
      go slowSquare(x, idx)
     }
     <-finished

     fmt.Println("First Result: ", results[0])
     fmt.Println("So-far Results: ", results)

    }


This is a data race between the write to `results[idx]` in the `slowSquare` goroutine and the read of `results` in the `prettyPrinter` goroutine.


This will wait up to 1 second before showing a result when a result comes in. I'm pretty sure Chris doesn't want any waiting like that.

This will also print some results multiple times. I think Chris wants to print each result once.


It is utterly clear that the random wait is not intrinsic to the logic - it was only added for demonstration to simulate varying duration of requests.

You can simply comment out the println and just pick the first results[0]. Again, the repeated println for all results was only added for demonstrative clarity.

Frankly, the above satisfies all primary goals. The rest is just nitpicking - without a formal specification of the problem one can argue all day.


>It is utterly clear that the random wait is not intrinsic to the logic - it was only added for demonstration to simulate varying duration of requests.

I wasn't talking about the random wait at all. I was talking about

      for range time.NewTicker(time.Second).C {
       fmt.Println("Results: ", results)
      }
>You can simply comment out the println and just pick the first results[0].

When should we look at results[0]? There needs to be some notification that results[0] is ready to be looked at. Similarly with all the rest of the results.

>Frankly, the above satisfies all primary goals. The rest is just nitpicking - without a formal specification of the problem one can argue all day.

I guess we have to disagree. From my reading of the blog post it was pretty clear what Chris wanted, and the code you provided didn't meet that.


You can completely ignore that pretty print function as it not essential to the goal.

Revised example without pretty-print at https://go.dev/play/p/LkAT_g95BLO

As soon as you have completed `<-finished` in the main go-routine, it means `results[0]` has been populated and is ready for read.

If you want to wait till all results are available, then perform `<-finished`, `len(results)` times. (Or use sync.WaitGroup)

    package main

    import (
     "fmt"
     "math/rand"
     "sync/atomic"
     "time"
    )

    func main() {
     args := []int{5, 2, 4, 1, 8}
     var resultCount atomic.Int32
     resultCount.Store(-1)
     results := make([]int, len(args))
     finished := make(chan bool, len(args))

     slowSquare := func(arg int, fnNum int) {
      randomMilliseconds := rand.Intn(1000)
      blockDuration := time.Duration(randomMilliseconds) * time.Millisecond
      fmt.Printf("(#%d) Squaring %d, Blocking for %d milliseconds...\n", fnNum, arg, randomMilliseconds)
      <-time.After(blockDuration)
      resultIndex := resultCount.Add(1)
      results[resultIndex] = arg * arg
      fmt.Printf("(#%d) Squared %d: results[%d]=%d\n", fnNum, arg, resultIndex, results[resultIndex])
      finished <- true
     }

     for i, x := range args {
      go slowSquare(x, i)
     }
     fmt.Println("(main) Waiting for first finish")
     <-finished
     fmt.Println("(main) First Result: ", results[0])
    }


Chris wants to report the results in the same order as the inputs. So 25, 4, 16, 1, 64. Your code will report the results in an arbitrary order.


Ok, but that is even more simpler and shorter with plain `sync.WaitGroup`.

    package main

    import (
     "fmt"
     "math/rand"
     "sync"
     "time"
    )

    func main() {
     args := []int{5, 2, 4, 1, 8}
     results := make([]int, len(args))
     var wg sync.WaitGroup

     slowSquare := func(arg int, resultIndex int) {
      randomMilliseconds := rand.Intn(1000)
      blockDuration := time.Duration(randomMilliseconds) * time.Millisecond
      fmt.Printf("(#%d) Squaring %d, Blocking for %d milliseconds...\n", resultIndex, arg, randomMilliseconds)
      <-time.After(blockDuration)
      results[resultIndex] = arg * arg
      fmt.Printf("(#%d) Squared %d: results[%d]=%d\n", resultIndex, arg, resultIndex, results[resultIndex])
      wg.Done()
     }

     for i, x := range args {
      wg.Add(1)
      go slowSquare(x, i)
     }
     fmt.Println("(main) Waiting for all to finish")
     wg.Wait()
     fmt.Println("(main) Results: ", results)
    }


    (main) Waiting for all to finish
    (#4) Squaring 8, Blocking for 574 milliseconds...
    (#1) Squaring 2, Blocking for 998 milliseconds...
    (#2) Squaring 4, Blocking for 197 milliseconds...
    (#3) Squaring 1, Blocking for 542 milliseconds...
    (#0) Squaring 5, Blocking for 12 milliseconds...
    (#0) Squared 5: results[0]=25
    (#2) Squared 4: results[2]=16
    (#3) Squared 1: results[3]=1
    (#4) Squared 8: results[4]=64
    (#1) Squared 2: results[1]=4
    (main) Results: [25 4 16 1 64]


As soon as results[0] is ready he wants to print it. Then as soon as results[1] is read he wants to print it. Etc. Not waiting till the end to print everything, and not printing anything out of order.


If results[i] must be printed only AFTER print of results[0]...results[i-1], then you effectively need to wait for max of (time to compute results[0]...results[i]), since even if results[i] is computed earlier you can't print it out if results[0]..results[i-1] are not available. If result[0] takes the longest compute time, then you will definitely need to wait till the end.

Frankly, a simple sequential for loop seems to the simplest solution here :)

Anyways, I think this: https://go.dev/play/p/lFBpzUVVzUj satisfies all the constraints. Only look at the output with the "(main)" prefix, the other prints are for elucidation.

        package main

        import (
         "fmt"
         "math/rand"
         "time"
        )

        type Result struct {
         value    int
         computed bool
         consumed bool
        }

        func main() {
         args := []int{5, 2, 4, 1, 8}
         results := make([]Result, len(args))
         signal := make(chan bool)
         signalCount := 0

         slowSquare := func(arg int, index int) {
          randomMilliseconds := rand.Intn(1000)
          blockDuration := time.Duration(randomMilliseconds) * time.Millisecond
          <-time.After(blockDuration)
          square := arg * arg
          results[index] = Result{value: square, computed: true}
          fmt.Printf("(#%d)   Squared %d, index=%d, result=%2d, duration=%s, sending signal. \n", index, arg, index, square, blockDuration)
          signal <- true
         }

         for i, x := range args {
          go slowSquare(x, i)
         }

         for {
          if signalCount == len(results) {
           break
          }

          <-signal
          signalCount++

          for i := 0; i < len(results); i++ {
           if !results[i].computed {
            break
           }
           if !results[i].consumed {
            fmt.Printf("(main) Squared %d, index=%d, result=%2d\n", args[i], i, results[i].value)
            results[i].consumed = true
           }
          }
         }

        }

Example Output

        (#0)   Squared 5, index=0, result=25, duration=8ms, sending signal. 
        (main) Squared 5, index=0, result=25
        (#4)   Squared 8, index=4, result=64, duration=210ms, sending signal. 
        (#1)   Squared 2, index=1, result= 4, duration=777ms, sending signal. 
        (main) Squared 2, index=1, result= 4
        (#3)   Squared 1, index=3, result= 1, duration=867ms, sending signal. 
        (#2)   Squared 4, index=2, result=16, duration=924ms, sending signal. 
        (main) Squared 4, index=2, result=16
        (main) Squared 1, index=3, result= 1
        (main) Squared 8, index=4, result=64


That code has a race condition. One goroutine can modify an element of results at the same time a different goroutine is reading it. Running with `go run -race` detects the race.

This can be fixed with a mutex.

Now that we've got working code (with the mutex), we have to ask: have we proved Chris wrong? I don't think so. Chris never said it's impossible to implement this in Go. Chris just said that implementing it in Go is uglier than using an array of promises in some other language. And I think this code is uglier than an array of promises.

Although I think I disagree with Chris. He says that an array of channels is significantly uglier than an array of promises. I don't think so. I think an array of channels is fine (and is easier to understand than the signal, signalCount, computed, consumed, +mutex code).

    package main

    import (
     "fmt"
     "math/rand"
     "time"
    )


    func main() {
     args := []int{5, 2, 4, 1, 8}
     results := make([]chan int, len(args))

     slowSquare := func(arg int, index int) {
      randomMilliseconds := rand.Intn(1000)
      blockDuration := time.Duration(randomMilliseconds) * time.Millisecond
      <-time.After(blockDuration)
      square := arg * arg
      fmt.Printf("(#%d)   Squared %d, index=%d, result=%2d, duration=%s, sending signal. \n", index, arg, index, square, blockDuration)
      results[index] <- square
     }

     for i, x := range args {
      results[i] = make(chan int, 1)
      go slowSquare(x, i)
     }

     for i := 0; i < len(results); i++ {
      fmt.Printf("(main) Squared %d, index=%d, result=%2d\n", args[i], i, <-results[i])
     }
    }


Yes, I didn't quite get why he wanted a solution without multiple channels..it is clearly the best way. The only other option without multiple channels is to use one of the atomic types: `sync/Atomic.Int32` (to avoid the mutex) and then a single channel for signalling.


API tool to automate all the stuff Postman makes painful: https://callosum.dev Spec generation from request logs, automatic schema generation and validation, test generation (eventually), totally offline, no accounts or cloud sync necessary!

Been taking longer than I hoped but should be released soon (next few days)


Re: the grid connection backlog - much of the challenge of turning on new generation is simulating the increasingly complex ways the grid can fail due to all the interconnections. It’s a huge computational challenge and there’s really not much incentive to speed it up


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: