HN user

danwilsonthomas

5 karma

[ my public key: https://keybase.io/danwilsonthomas; my proof: https://keybase.io/danwilsonthomas/sigs/iq1u_qrCGSXh7E9WBto5I9wre8QdFDYOcAruZEpmaGs ]

Posts0
Comments9
View on HN
No posts found.

I've done a version of this in Godot. It's kind of a hack, but it works and it's all Nodes without needing a script. You could do it more easily in a compositor effect but you have to deal more with the guts of the engine.

The basic idea was to use a chain of Viewports and TextureRects using the previous ViewportTexture to do the effect. This is essentially just setting up a chain of framebuffers that I can draw on top of at each step. The first step just does a simple calculation to convert the incoming color to an "energy" value with a simple shader on the whole frame. Then there are two decay viewports that feed each other to decay the old frame and overlay the new one. These just have a decay parameter that I can tweak to get different effects. There's a final Viewport that supersamples everything because I'm going for more of a vector display look than a CRT. And I can layer on other effects at either the energy state (like decaying phoshpor) or at the final stage (like flicker or screen curve).

Here I've exaggerated the decay quite a bit so you can see the effect. https://www.youtube.com/watch?v=2ZrvcZIfqOI The trails there are entirely from this effect. I'm only rendering the spinning figure. You can also see where lines overlap they are brighter than the surrounding lines because I cap the maximum energy value in the first layer.

I've seen something similar when using Pydantic to get a list from structured output. The input included (among many other things) a chunk of CSV formatted data. I found that if the CSV had an empty column - a literal ",," - then there was a high likelihood that the structured output would also have a literal ",," which is of course invalid Python syntax for a list and Pydantic would throw an error when attempting to construct the output. Simply changing all instances of ",," to ",NA," in the input CSV chunk reduced that to effectively zero. A simple hack, but one that fit within the known constraints.

Imagine you have users that want to view an XML document as a report of some kind. You can easily do this right now by having them upload a document and attaching a stylesheet to it. I do this to let people view after-game reports for a video game (Nebulous: Fleet Command). They come in as XML and I transform them to HTML. Now I do this all client-side using the browser support for XSLT and about 10 lines of javascript because I don't want to pay for and run a server for file uploads. But if I did the XSLT support in the browser would make it truly trivial to do.

Now this obviously isn't critical infrastructure, but it sucks getting stepped on and I'm getting stepped on by the removal of XSLT.

I've seen this idea a lot from members of my team that when you use a state management library then absolutely everything has to go through that state management library. This results in some of that pain you describe with state that's specific to the UI. If you're still working with React, try keeping that menu or input state in the component with setState instead of putting it in the global state store. I've found that simple change to clarify our projects immensely by cutting out lots of unnecessary code and highlighting what state is actually important to the functioning of the application.

Part of the reason why I personally was so upset by the talk was that it felt as if there was no room for discussion or debate on the points raised. In addition there was what felt like a lot of sniping towards features of statically typed languages that felt designed just to get a reaction from the crowd. The fact that there was an entire slide designated to tearing down a series of videos by SPJ felt not only irrelevant, but also disrespectful. There seemed to be a lack of willingness to meet halfway and concede there was anything useful from the other side.

Perhaps most frustratingly, I know that RH is capable of much better, much more informative presentations. There might have been something worthwhile in here, but the tone, style, and majority of the content didn't make it worth digging out in my opinion.

Regarding Haskellers' attitudes, I'll add that I haven't seen anything like what you describe at least on the Haskell subreddit. It could be happening in other forums but by and large it's been a welcoming community even to those that come in skeptical.

This should be really interesting, considering that Code Review and Automated Tests are far more likely to root out important bugs than a type checker.

Not to bring up the tired argument again, but as far as I know there isn't proof for this. Nor is there proof that types are better at catching bugs than tests or code review. The best we can hope for is anecdata. I've got my fair share that shows types in a good light and I'm sure you've got your fair share that shows tests in a good light.

That being said, global type inference is in my opinion a fundamental game changer here. No longer are you required to put effort into your types up front. It is the only one of those three tools with that property. This makes it trivial to get "100% coverage" with types. That is why people argue for static type coverage.

Additionally, Haskell specifically has a switch to defer type errors to runtime, which means you can compile and test without 100% coverage.

I did no such thing. I picked a `foo` to prove a point: That the type inference engine is more than capable of inferring every type in a reasonably sized program, and the toy examples we are dealing with are below a minimum threshold. It had nothing to do with satisfying the type checker or finding some "consistency". Furthermore, the larger point was simply that the "type burden" is often not a burden at all and can in fact be completely invisible to you the user. Look at https://hackage.haskell.org/package/turtle-1.4.4/docs/Turtle... and see how very few of the examples have explicit type annotations, and when they do they exist for explanatory and documentation purposes only.

Please don't assume the motivations or intentions behind my actions and then attempt to use that to prove a point.

To address that point explicitly: In general, if requirements change then the code needs to change. Neither Clojure, nor Haskell will make that fundamental truth go away. You say you want to create less work for yourself when this happens. I'd like to propose that there is different types of work with differing qualities. When you receive a requirement change, the first step is to determine what that means for the system as it stands. This is by far the hardest step and is where the majority of the "hard" work lies. The next step is to change the system to fit the new requirement. This is primarily a mechanical task of following directions you laid out in the first step. I think it's preferable at this point to have some tool or process, even multiple, to make sure you do this part correctly. Tests, types, and code review are the three major tools I've seen aimed at this problem. Tests help ensure you've specified the changes correctly by forcing you to write them down precisely. Types help ensure you've implemented the changes correctly by forcing consistency throughout the system. Code review helps ensure you've implemented the changes in a manner that is understandable (maintainable) by the rest of your team. Note also that tests require an investment in writing and maintaining them, types (with global type inference) require no upfront investment but can require some maintenance, and code review requires participation of team members of similar skill level to you. They also all require some level of expertise to be effective. It seems shortsighted to throw away static types when they are, in my opinion, the cheapest method of helping correct implementation.

I was thinking about this sentiment:

  And that "one single place" is for this trivial toy program. Now imagine the impedance across the delivery of a real business app.
That seemed very incongruous with my experience. I think the problem is that in my experience the number of types necessary scales sub-linearly with the amount of code. This is because Haskell (and other languages with what one might consider pleasant type systems) generally have full type inference. So while you might need the explicit type for the toy example, scaling that example up means only a handful, and possibly a net negative, other necessary explicit types.

This comes up in a concrete manner in your example. The add function is almost too small to be typed appropriately. There isn't enough information for GHC to constrain the choice of numeric representation so it picks one. If you were to embed that in a larger program with more specific operators no types are necessary.

  Prelude> let add = (+) <$> readLn <*> readLn
  Prelude> let foo = (/) <$> add <*> add
  Prelude> foo
  3
  30.1
  40
  10.2
  0.6593625498007968
So it's quite possible that the "type burden" becomes smaller as your system grows.

I tried this, because my first thought was that this was much the same as the first example. Certainly Haskell doesn't have a variadic addition function by default but I don't think that's the biggest concern here (variadic addition is easily just summing a list). The surprising thing was entering both an "integer" and a float to be read. You certainly can parse floats from input, but this: `(+) <$> readLn <*> readLn` doesn't do it, even though at first glance it seems like it should. If you wanted to handle fractional values, you would have to hint to the type checker that you wanted to do so. I do think that this example is perhaps too specific, and highlights only the specific topic of dynamic number conversion which is, depending on the situation, both blessed and cursed. I will concede though that if you want dynamic number conversion Haskell isn't going to give you that easily.