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

Curious what you want from a jailbreak at this point? It seems all of the old things that were actually helpful have found them into first party software, or at least in the sanctioned third party. (internet tethering, emulators, side loaded apps, etc.)


I thank goodness I haven't been subjected to (mobile) Apple for many years in order to speak to what they "allow" and don't nowadays, but the short answer is that I want to *fucking own* the hardware I pay fuckloads of money to purchase

Also, while I was typing out the "I want f-droid.org for iOS" I realized there is a pragmatic answer: I want to build and distribute apps without having to pay for the right to do so, not because I am stingy but because paying is gatekeeping. Do you know how much the Joplin devs have to pay Google to put this .apk link here[1]? $0

1: https://joplinapp.org/help/install/#mobile-applications


That’s a web app, it’s on them if they don’t want to offer their services without requiring me to let them out of the web sandbox.


I presume you didn't follow the link I pointed to, which for sure points an .apk URL

Now, if you're trying to be extra cute by saying "react native is a webpage with more steps," I'm not trying to have that fight, but I can assure you with 100% confidence that Joplin's apk loads without Internet connectivity, making it not meet my definition of "a web app"


I did in fact follow the link. I actually went further than you, all the way to the GitHub, where I saw that it was actually just a web app. All TS.

And you can 100% load PWAs offline, which might be slightly more progressive than your idea of a web app, but I hope not overly so.


Wow, you have completely missed the parent poster's point!

Whether or not the specific app used as an example happens to be a webapp holds no relevance to what he was saying overall.


Internet tethering is actually one that's gone backwards, with "unlimited" cellphone plans that are limited when tethering. Or plans that disable tethering even though all the software is there.

The other thing from a jailbreak would be for it to become self hosting, that is, give the ability to make full blown iPhone ipa on an iOS device without needing a macOS device anywhere at all.


> Internet tethering is actually one that's gone backwards, with "unlimited" cellphone plans that are limited when tethering. Or plans that disable tethering even though all the software is there.

The internet tethering limits are in the contract though. Surely the primary goal for jailbreaking isn't just theft of service?

> The other thing from a jailbreak would be for it to become self hosting, that is, give the ability to make full blown iPhone ipa on an iOS device without needing a macOS device anywhere at all.

You can create full-fledged local apps, or publish them for free or sale in the App Store using Playgrounds on an iPad.

The primary limitations are in things like typing speed and screen real estate as well as UX complexity and side-by-side debugging, none of which directly change with an active jailbreak.


> The internet tethering limits are in the contract though.

I don't recognize that 10 GiB downloaded via my phone, or via my laptop connected to my phone as different, no matter what the contract says.

> on an iPad.

I said iOS not iPadOS, but that's good to know.


As someone who does a ton of networking/routing at the link layer for a day job, I can definitely see why they’re taking measures to reduce bandwidth hogs - to the extent I might actually prefer to be on a network that has taken measures to reduce hogging vs one that has not.

When it really truly matters, like when I have a business need to download huge items in remote areas, the $10/GB+ justifies itself.


Ironically, downloading huge items is easy to do without tethering.

And video streaming, probably most people's biggest bandwidth use, fits very well on phones.

Does anyone offer a tethering plan that's rate limited but not data limited?


> Does anyone offer a tethering plan that's rate limited but not data limited?

T-Mobile in the US; they give you a set amount of high-speed tethering based on your plan, then it rate limits severely until the end of the billing cycle unless you upgrade your plan or buy a a pack of data.


Okay, 600kbps is acceptable. Beats a lot of networks. Things are improving since I last checked.

Though with how happily and loudly they've made 1.5Mbps video excluded from data caps, it would be better if the throttled speed was at least 1.5Mbps.


Verizon after 60GB is 600kbps on 5G/4G but up to 3Mbps on UWB. Pretty reasonable IMO. It’s AT&T that’s the holdout, 128k regardless of plan, after 60GB.


> they’re taking measures to reduce bandwidth hogs

My problem with this is that it's the wrong measure.

There's no good technical reason to shape traffic to a specific rate irrespective of network conditions or capacity. All of the links in the chain support QoS.

Shaping a bandwidth hog to a tier below the rest of the users makes sense, but that's not what's going on.


I have a fairly simple list:

- a weekly, scheduled reboot of the device

- an iMessage addon that can auto-respond with `stop` to every political spam message I get

- an iMessage addon that would let me filter, categorize, and display messages in a manner I choose instead of a Big Dumb List, like we've had for email for decades

- an iMessage addon for services like Beeper

- an iMessage addon that ... I smell a pattern


There is one particular edge case in which they do not satisfy the laws. That happens to make them much more practical in day to day coding than a strict interpretation would be.


So having Promises be monads would make them less useful? This is an interesting point.

It seems to confirm the argument that monads are not that useful or pervasive outside of Haskell.


We might consider promises “applied monads” or “engineered monads”. Monodic in inspiration and they solve the same core problem, but they aren’t straightjacketed into the ivory tower “laws” in absolutely every edge case (they do satisfy them in the vast majority of cases). Which is good, because it means we never need to write things like “await await await await await foo()”


> Which is good, because it means we never need to write things like “await await await await await foo()

But what your describing is the actual value proposition of monad though! It's literally the construct you use to avoid excessive unwraps.

Look at the type signature of bind:

  m a -> (a -> m b) -> m b
It returns m b. Not m (m b). Not m (m (m b)).

> but they aren’t straightjacketed into the ivory tower “laws” in absolutely every edge case

In other words, you can't abstract over them. If you have libraries that manipulate monads, they will be incompatible or buggy, because they will assume (by design) behaviour that a non-monad will not live up to.


I should not need to explain to you that the problem is when b itself is internally an (m b’). Or when it is a sum of many types, of varying m-nests.

I can assure you that JS libraries work with Promises just fine.


This is all besides the root topic though, which is that CPS and Monads are isomorphic. Which is true. Promises are a different thing.


That sounds a lot like async/await. Any errors thrown in an async context bubble to the callsite awaiting it.


Sorry you are right, that was specific to our case where we were already waiting for a result from the goroutines. In an actor model, you would let the "goroutine" die and the supervisor would restart it if it has a restart policy, otherwise it will die and stay dead (without bringing the system down). In erlang you can also "link" the current actor to another actor so that if the linked actor dies, the linker dies too (or receives a message)


F# first actually. Then C#. Then Haskell. Then Python. Then TypeScript. Parent just has an axe to grind.


Coming from a heavy TS background into a go-forward company, I’d say the main thing you get with async is it makes it incredibly obvious when computation can be performed non-sequentially (async…). For example, It’s very common to see the below in go code:

   a := &blah{}
   rA, err := engine.doSomethingWithA()
   b := &bloop{}
   rB, err := engine.doSomethingWithB()
This might have started out with both the doSomethings being very quick painless procedures. But over time they’ve grown into behemoth network requests and very thing is slow and crappy. No, it’s not exactly hard to spin up a go routine to handle the work concurrently, but it’s not trivial either - and importantly, it’s not immediately obvious that this would be a good idea.

Contrast to TS:

   let a = {blah}
   let [rA, err] = engine.doSomethingWithA()
   let b = {bloop}
   let [rB, err] engine.doSomethingWithB()
Now, time passes, you perform that behemoth slowing down of the doSomethings. You are forced by the type system to change this:

   let a = {blah}
   let [rA, err] = await engine.doSomethingWithA()
   let b = {bloop}
   let [rB, err] await engine.doSomethingWithB()
It’s now immediately obvious that you might want to run these two procedures concurrently. Obviously you will need to check the engine code, but any programmer worth their salt should at least seek to investigate concurrency when making that change.

I wouldn’t be bringing this up if I hadn’t made 10x+ performance improvements to critical services within a month of starting at a new company in a new language on a service where the handful of experienced go programmers on the team had no idea why their code was taking so long.


Of course, the other nice thing about the JS example compared to Go is that it's trivial at the callsite to do this:

    const [[rA, errA], [rB, errB]] = await Promise.all([
      engine.doSomethingWithA(),
      engine.doSomethingWithB()
    ])
At least these days you can ask an LLM to write the WaitGroup boilerplate for you in Go.


Which has to do with the incredible lack of expressivity of Go, not with the concurrency model. Nothing precludes doing exactly the same thing with thread-like constructs in an expressive language.

Not to mention waitgroups are way overkill for this. You’d just use a channel or two. Or an errgroup if you want to be fancy.


Indeed. And breakpoints and stepping across concurrent context actually works in JS, which is nice.


WaitGroup/ErrGroup doesn't even work here, because the functions return data. I mean, you can use ErrGroup, but it requires additional error-prone concurrency orchestration to work.


Yeah, go's a little boilerplatey, but you have to option to run two sync things concurrently as well with something like:

    type result[T any] struct {
        el T
        err error
    }
    chanA := make(chan result[aResultType])
    chanB := make(chan result[bResultType])
    go func() {
        defer close(chanA)
        a := &blah{}
        rA, err := engine.doSomethingWithA()
        chanA <- result[aResultType]{
            el: rA,
            err: err,
        }
    }()
    go func() {
        defer close(chanB)
        b := &bloop{}
        rB, err := engine.doSomethingWithB()
        chanB <- result[bResultType]{
            el: rB,
            err: err,
        }
    }

    resultA := <- chanA
    resultB := <- chanB


One could theoretically pull out the shared boilerplate to a utility function like:

    func runTask[T any](task func() (T, error)) chan result[T] {
        ch := make(chan result[T])
        go func() {
            defer close(ch)
            res, err := task()
            ch <- result[T]{ el:res, err:err } }()
        return ch }
Does that sort of thing happen much in practice?


Yes, it does, and Go is perfectly capable of it, and many libraries exist for you to choose which exact method suits your problem and temperment.

One of the common pasttimes in the threaded versus async debate is to present code in which one side uses all sorts of helpers and patterns and libraries and the other side is presented through writing it "raw". The great-grandparent of my post here is guilty of this. While there are interesting reasons to debate threaded versus async code, this is not one of them. Both of them are absolutely capable of writing the moral equivalent of "output = parallel_map(myMapFunc, input)" and all similar operations to within practical epsilon of each other, and anyone citing this sort of thing as an argument on either side should probably be ignored. And both languages will feature code written by people who don't know that, and it shouldn't count against either.


No… I fear you’ve missed the entire point of the matter, which is that async/await requires that you must go all the way up the call stack explicitly “await”ing things when you have introduced an “async” call (or similar wide spread changes to that effect). There’s no special magic utility function you can call you hide it away. That’s the whole point – and a very good thing, this thread argues.


No, it is perfectly feasible to abstract around it. It's just that the abstractions are also colored. But there is no more a rule that you can only "await" a promise right in the exact code where you created the promise than there is that the only way to use threads is to spawn them right on the spot and then wait for the result right on the spot. Critics of both async and threads are just dead wrong on this, and observably, objectively so, since libraries in both cases not only exist, but are readily available and abundant.

And I'm admitting this "against interest", as the lawyers say. I'm not striking a disinterested "middle of the road" pose here. I'm hugely on the side of threads. But it is still not a relevant criticism of async. You can easily "parallel map" with either abstraction and you are not stuck unable to abstract the control flow in either case.


Show me a JS library that allows you to swap a non-async call for an async one in a non-async context^ and I’ll eat my hat.

^Without any non-local changes, obviously.


You can, but I didn't want to introduce any extra constructs from the original example.

And this is a bit of a weird case, at least where I am. I tend to have a bunch of things to process and have one goroutine sending keys/indices/etc to a channel that multiple workers are processing off of.

We did have an abstraction for that at one point, but there were enough edge cases in our domain that we either had to develop a config system or rip it out and go back to writing each one (we went with the latter after an attempt at the former went really bad).


Now handle the following that is painlessly solved by runtimes with structured concurrency:

If A failed, the whole function is failed, and we don't need B any more. To save resources we should cancel B. And vice versa, cancel A if B failed.


    import github.com/carlmjohnson/flowmatic

    func whatever(ctx context.Context) {
        err := flowmatic.Race(ctx, taskA, taskB)
    }
Provide your definition of taskA and taskB, of course.

As I said in another message, this is not a particularly fruitful line of attack in either direction. All the languages in question are perfectly capable of abstractions.


I don't like that the return values of the tasks has to be communicated with side effects, but I'll concede that it's quite painless.

I guess I'm just still salty when someone commented (in another post a long time ago) that golang only has `go` (compared to `launch`, `async`, and `coroutineScope` in Kotlin) and is simpler.

> All the languages in question are perfectly capable of abstractions.

I don't think async functions in JS can be cancelled though.


"I don't like that the return values of the tasks has to be communicated with side effects, but I'll concede that it's quite painless."

Me neither, however, it is generally the most flexible approach and I can see why a library takes it. If you want to communicate it via the return, you also have to impose a restriction that the tasks all return the same type. I think it makes sense for a library to work this way because you can easily add this around the library call yourself, but it's more difficult to go the other way. (Not impossible, just more difficult.)

"I don't think async functions in JS can be cancelled though."

Poking around, it looks pretty hard.

That said, cancelling in generally is very difficult in imperative languages, so even as someone who finds async in JS quite distasteful I can't dock too many points. Go basically just reifies the JS solution into a standard community practice, which is definitely an improvement since you can largely rely on it being supported everywhere, but one could reasonably debate how good it is. It is occasionally a problem that if you want to cancel an ongoing computation you may have to have your code manually check a context every so often, because there's no "real" connection between a context and a goroutine.


If you’re fine with manually checking a standard interface to see if you should abort, JS’s answer is the AbortController. This is supported by features like the “fetch” function for making cancellable http requests.

https://developer.mozilla.org/en-US/docs/Web/API/AbortContro...


So, context.Cancel?


Only if those doSomething methods were written as asynchronous to begin with.in your original example, doSomethingA was simple, why would it be an async method. If your answer is write every method async for a rainy day, then whats the point.


No… that’s the whole point. If you change them to be async, the language forces you to go and rethink what implications that has for the callers. This is a good thing, dumbly sequenced operations are terrible UX. And UX is far more important than whatever it is they call “DX”.


I’d rather just stick to languages that don’t have colored functions. You don’t need to be forced to think about this. Your tools manufactured this issue.


Honestly, despite that blog, async coloring is a feature. The pattern enforces implicit critical sections between yields and the coloring is how the dev can know what will yield.


This is a really interesting point. You almost never hear async function coloring being conceptualized as a feature rather than a hindrance. Async function coloring is kind of analogous to the borrow checker in Rust. It makes you think about concurrency the same way that the borrow checker makes you think about memory ownership and lifetimes.


async is a great feature if you use it from square 1. If you start with a legacy codebase using callbacks and try to port it incrementally to async, you're gonna have a bad time. Otherwise, it's definitely a feature


Yeah upgrading a legacy codebase that uses callbacks is not fun, but if the callback functions follow the Node error first value second convention, then it's a little bit easier because you can just use `util.promisify` to convert them to promises in Node. There's also the new Promise.withResolvers method which helps a bit too [1].

[1] https://github.com/tc39/proposal-promise-with-resolvers


Yes. That blog has probably done more to negatively impact the industry than any other written work I know.


For some reason requiring the programmer to use additional syntax at the call site to mark behavioural properties of called functions is not a popular language feature generally. I guess eg TypeScript could add it as a user extensible feature. Would it be useful to be able to require things like this in your internal API?

  let gizmos = nocheckperms lookupRequestedGizmos(request);


You're trading complexity for expressiveness but the await keyword syntax is essentially unwrap sugar. Dereferencing a pointer is similar syntax.

It's possible to write a language where awaiting a task is done through a method on the task type. I don't think this is ideal because the whole reason you're using explicit yield points is so you can tell when something yields. Using method syntax makes that harder to see at a glance.


This makes me want an LLM that traces every line of code running on a site and provides a summary…

“when you scrolled the page 2 pixels just now, it triggered 500 network requests to various tracking sites. additionally, it forced redrawing the site 3 times, and consumed 400ms of GPU time.”


Why on earth would you involve a larger waste of resources to summarize something which is capturable and displayable without?


I’m happy to sift through a .cpuprofile and create an intelligent actionable summary of it if I’m at work and that’s the job they’re paying me to do. And very often they do pay me quite handsomely for my ability to do just that!

But if I’m not at work, I frankly don’t care enough. A single button “take trade for 1 second and send it to GPT4” is the level of effort I’m willing to freedom dedicate to these folks’ crap.


What’d be really great is a codegen aspect. A non-negligible part of any data munching operation is “this input object has fields X, Y, Z and we need an output object with fields X, f(X), Y, f(Y,Z)”. This is something and LLM has a decent chance at being really quite good at.


Watch out with the array mode though, according to OpenAI docs it technically can return the results in any order and you must sort them by index to be sure you have the right associations. I’ve never seen them out of order in practice, but it’d be entirely in-character for them to suddenly change that sporadically and without warning, and now your entire vectordb may or may not be nondeterministically ruined.


Yikes!


You mean the question? Everyone should be able to see the answer of the active clue.


Nope. Everyone should be able to see the clue, but not the answer or correct response to the clue.


I believe that explaininjs is making a pedantic/semi-tongue-in-cheek joke about the gimmick of Jeopardy where the clues are "answers" and contestants buzz in to respond with the "question". https://youtu.be/KDTxS9_CwZA?t=156


It would be pedantic if it were indeed nothing but a joke, but I think the existence of this gimmick forces us to assimilate our non-joking language in order to avoid ongoing problems. Easier to have common ground among participants and onlookers than not. To exclude yourself, should you wish, simply stick to clue/response.


I’ve had astigmatism ever since a laser eye surgery. It’s unnoticeable 95% of the time, but every site that forces dark mode on me makes it immediately obvious. I immediately find a light switch or leave.


Ah man that's why laser eye surgery scares me. My eyes are pretty bad so a lot of my lens has to be shaved. I noped out of last consultation; they recommended I get a lens inserted behind my cornea cause of that, but there are so many horror stories online


I wouldn’t call my case a horror story. This is still a million times better than how it was, and I’d absolutely go back do it again.

In fact I am considering getting a “tune up” round, but it’s a lot of money and leaves your eyes out of operation for quite a while. Alternatively, I might get astigmatism glasses that I wear exclusively in places I need to see well in low light, for instance movie theater credits are always a challenge (which would be a non-issue if my GF didn’t make us sit through all the credits to every movie we see…)

There’s nothing quite like the quality of life delta that comes from waking up every morning and simply being able to see.

Oh, and with periods of concerted effort I’ve been able to temporarily remove the astigmatism entirely, by basically training my eyes how to focus. There’s a good chance continuing to do that more frequently would give me a permanent fix too.


Ah I didn't mean your experience was a horror story; I've read others that are


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

Search: