8,134 karma · joined May 1, 2010
https://twitter.com/jasim_ab
jasim.ab@gmail.com
This can be improved by using coarse-grained actions like say `UpdatePerson(person)`. And this new `person` value can be obtained by the event handler itself when the user changes the name. However, the actual logic can sit inside a Person module, allowing us to do something like: `onChange=dispatch(UpdatePerson(Person.setFirstName(newName, currentPerson))`
This approach lets us keep the behaviour close to the view while encapsulating the actual implementation inside a rich domain model.
In fact I never thought I'd say this - for the first two years of discovering sum types, I thought they were the silver bullet of computing, and certain experiences have sadly tempered me there :)
I still agree that types _can_ make the flow and transformation of data in the system clearer than if there were no types. Though without experience, we could very well end up with convoluted designs and that can be as difficult to untangle as a dynamic spaghetti.
Second is GitHub - the first few hundred commits of many successful open-source projects. It is a wonder to see sprawling codebases starting at its first commit, and plodding its way over years before gathering momentum.
When we complain about universities not preparing students better for jobs, what we really mean is that universities are not doing the bare minimum that they should be doing - in case of CS, students should at least know how to program well, and be well versed in the practicalities of computing. That does not exclude learning the fundamentals (which is often denigrated as "theory").
It is just that students often have neither the theory nor the practice, and at a minimum, we're asking, they should know the practice so they can at least be useful in their jobs.
Internally the software would be a ball of mud, a fragile patchwork of incoherent code that nobody wants to touch. A lot of these programs become immutable logs - if you want to change a feature, you add more code because you're afraid to touch anything that already exist.
Yet they are robust - all the bugs in the commonly traficked code paths have already been sussed out, and from outside the software looks like an impenetrable, rugged piece of craftsmanship. This has happened to me in the early years of my career (proud of the robustness, not proud of the code), and I've witnessed it so many times outside. So yeah - robustness and correctness is either a matter of correct-by-construction, or correct-over-time.
alias verydull="~/Software/ddcctl/ddcctl -d 1 -b 3 -c 15"
alias dull="~/Software/ddcctl/ddcctl -d 1 -b 6 -c 35"
alias decent="~/Software/ddcctl/ddcctl -d 1 -b 10 -c 40"
alias medium="~/Software/ddcctl/ddcctl -d 1 -b 25 -c 50"
alias bright="~/Software/ddcctl/ddcctl -d 1 -b 30 -c 50"
alias morebright="~/Software/ddcctl/ddcctl -d 1 -b 35 -c 60"
alias superbright="~/Software/ddcctl/ddcctl -d 1 -b 100 -c 80"I mean - who cares about correctness when I'm trying to ship yet another cookie-cutter web application that I don't even know is going to see more than a thousand users in its life.
What I cared about was speed. A few bugs in production is a better trade-off than all the mathematical mumbo-jumbo and slow, meticulous programming that Typed FP seemed to demand.
But to my surprise, after getting started with ReScript/Reason/OCaml, I recognized that correctness is just a side-effect (!) of this mode of programming. (Note that unlike Haskell, OCaml is an imperative programming language with as much or as little mutation as we need. We can almost line-by-line translate a regular mutation-heavy piece of Python or JavaScript code to OCaml, if we wanted to.)
Typed FP, contrary to what I'd came to expect, is all about speed. Quoting from something I wrote a while ago:
"Refactoring a typed FP program is safe, but menial. When we say safe - it means no anxiety. There is going to be tons of mechanical work for every major type refactor - going thru all the compiler errors and fixing them one by one. That can't be avoided. We've been in multi-day refactoring sessions where we had to dredge thru page after page of compiler errors before we could even run the application. But - it is safe - we know that once it compiles, there won't be any mistakes.
That gives us the freedom to build fast and loose - and take stock periodically before we have to abstract things out and tidy up the place. We should compulsively rely on that safety. Typed FP forces us to go slow in places where a dynamic environment would've allowed us to blaze through. For all that trouble, we get unmitigated refactoring dexterity and we must exploit it to benefit from the paradigm."
There are many places where a dynamic, imperative approach is faster than a typed FP approach. But there are as many or more places where it is the other way round. I'm now fastest with this way of programming than anything else, and I wish that more literature around Typed FP communicated this rather than go about the indirect route of correctness, and expect people to pick up that correctness-by-construction results in increased velocity. That's a difficult jump to make unless one has written non-trivial amount of code in that style.
This book by Sandy - I've read the introduction and skimmed thru the Tile construction equations - is to me poetry. I'm having trouble fitting in the idealistic notion of the Escher tile to my imperfect real-world domain, but otherwise, what a book!
Even people who started out by grabbing everything they can get learn to be selective with their clients later. This is just enlightened self-interest -- small companies can only work with a limited number of clients every year, and you want those projects to do well and the customers to be happy with you. This is simply because this is a word-of-mouth, relationships based industry. Every project that does not do well is an opportunity lost in building a healthy pipeline down the line.
This dynamic however goes out of the window as the company becomes successful and becomes brand-driven - customers reach out because of the aura of the company and not necessarily because a friend of a friend talked about how you once helped save their company from a cliff.
"I'm suspicious of any plan to fix unfairness that starts with 'step one, dismantle the entire system and replace it with a better one,' especially if you can't do anything else until step one is done. Of all the ways that people kid themselves into doing nothing, that one is the most self-serving."
In Reason/OCaml, you can create a module interface (.rei or .mli) which makes the type opaque to functions outside the module. So instead of saying `type t('a) = list('a)`, it'll just say `type t`.
The compiler relies on this type information to decide whether functions are well-typed. So if an external module presumes to know the structure of the type and does an operation on it, it becomes a type error because the compiler simply doesn't know about the actual type. This is similar to encapsulation in OO where we don't expose a field to the outside world, but here it is done by virtue of the type signature itself, which I found to be a more powerful guarantee of encapsulation.
The module can then expose functions like `append` which would be the only way you can manipulate the list. This function in-turn can ensure the postcondition, guaranteeing that the list stays sorted. At the point in which we want to use the list for functions not supported by our SortedList module, we can turn it into a regular list with a `to_list` function. Since the underlying type is already a list, the operation is virtually free. The function would look like `let to_list = (xs: t('a)) => (xs: list('a))`. It is an identity function, with just the type changed for the compiler.
Similar rules about `append` applies to the constructor: we can create a new `SortedList` only with a `SortedList.make` which can ensure the postcondition. There will be no other way, thanks to the type being hidden, to create a value of the `SortedList.t` type.
I think it is the most accessible explanation of the marvel of Smalltalk, for those who were not lucky to work with it during the late 80-90s.
Also I think Ruby is the mainstream language that is closest to Smalltalk today, with the idea that everything is an object and late-binding as much as possible.
One thing I can't help point out is that Smalltalk / Alan Kay's vision of interconnected objects forming a recursive computing system is not the only "true" vision of OO out there. From Simula thru C++ and then Java and C# also are object-oriented (or class-oriented for those who care about the distinction), but they are statically typed. It is also OO because OO is a word, not a mathematical definition, and when people talk about OO, there is enough similarity between all these variations that we consider all of them to be in the same camp. Plus with the growing move towards strict static typing even in interpreted languages, which I think is driven by actual industry needs and programmer preferences than external marketing, it is becoming a disservice to think of statically typed OO languages as being somehow inferior to the true ideal of object-oriented programming.
But - there is a reason we've evolved to perceive reality in this way. It allows us to survive. Material concerns - health, wealth, status etc. and extractive consumption which messes up "nature", and having ego/identity distinct from nature -- all of this serve useful, vital purposes. A person who's constantly tripping will find it difficult to survive both in modern and ancient times long enough to propagate their genes.
People search for profundity in these experiences, and sure there are some. But to really understand the world and nature, I think the lens of science - observation, empiricism, and the scientific process is a better bet. There unfortunately really isn't anything _deeper_ about life. And whatever there is, personal introspection aided by psychedelics cannot hold a candle against organized human scientific endeavor.
Also, messing with one's brain is dangerous. It is one of the least understood human organs, irreplaceable, and fundamental to life like few other things.
Programming Paradigms and Beyond, Shriram Krishnamurthi and Kathi Fisler:
OO is a widely-used term chock-full of ambiguity. At its foundation, OO depends on objects, which are values that combine data and procedures. The data are usually hidden (“encapsulated”) from the outside world and accessible only to those procedures. These procedures have one special argument, whose hidden data they can access, and are hence called methods, which are invoked through dynamic dispatch. This muchseems to be common to all OO languages, but beyond this they differ widely:
* Most OO languages have one distinguished object that methods depend on, but some instead have multimethods, which can dispatch on many objects at a time.
* Some OO languages have a notion of a class, which is a template for making objects. In these languages, it is vital for programmers to understand the class-object distinction, and many students struggle with it (Eckerdal & Thune, 2005). However, many languages considered OO do nothave classes. The presence or absence of classes leads to very different programming patterns.
* Most OO languages have a notion of inheritance, wherein an object can refer to some other entity to provide default behavior. However, there are huge variationsin inheritance: is the other entity a class or another (prototypical) object? Can it refer to only one entity (single-inheritance) or to many (multiple-inheritance), and if the latter, how are ambiguities resolved? Is what it refers to fixed or can it change as the program runs?
* Some OO languages have types, and the role of types in determining program behavior can be subtle and can vary quite a bit across languages.
* Even though many OO aficionados take it as a given that objects should be built atop imperative state, it is not clear that one of the creators of OO, Alan Kay, intended that: “the small scale [motivation for OOP] was to find a more flexible version of assignment, and then to try to eliminate it altogether”; “[g]enerally, we don’t want the programmer to be messing around with state” (Kay, 1993).
In general, all these variations in behavior tend to get grouped together as OO, even though they lead to significantly different language designs and corresponding behaviors, and are not even exclusive to it (e.g., functional closures also encapsulate data). Thus, a phrase like “objects-first” (sec. 6.1)can in principle mean dozens of wildly different curricular structures, though in practice it seems to refers to curricula built around objects as found in Java.
Yet none of this is the one true OO. All of it remains a way of describing a human mode of expression, and so is rightly subjective.
But life is too short to always start at first-principles and rebuild civilization brick by brick.
Frameworks have three things: the code artifact: a Rails or a React, then the reified knowledge it abstracts: like how to structure a web application, and the community and their cultural knowledge.
Sometimes it makes sense to forego all of them and make it your own - especially when that layer of abstraction is core to the value you're creating and you want complete creative control over it. But often times they can be the difference between business success and failure.
He's written articles like https://www.newyorker.com/magazine/2018/12/10/the-friendship... which was one of the best pieces of writing on technology and humans that I've read recently. This is not the work of a recluse (the undesirable negative connotations aside).
(The Writing Life by Annie Dillard)
If most operations require data joins then pick a de-normalized structure. Then most data is often available with a single hash lookup. Though this makes updates difficult and mistakes can and often do lead to inconsistent data.
A normalized data-structure which use ids to refer to related elements makes it easy to add, update, and delete entities; but reads might require multiple joins which in turn makes the code complex.
We can choose between the two by listing out the possible operations and deciding which cases are more frequent. But this is often a moving target in a growing application.
The best approach I've so far found is to design data structures in a way that "invalid states are impossible". I will not link to Yaron Minsky and Richard Feldman's excellent talks on this here. This principle often results in elegant data structures that would've eluded me otherwise.
The second addition that is necessary is to use a statically typed language. Elm, Reason, and PureScript are the only choices in front-end at the moment because of soundness, sum/union types, and exhaustive pattern matching. A typed functional language makes refactoring an easy, mechanical, and reliable process which suddenly makes our code much more malleable and hospitable than before.
This is quite doable for us at Protoship (we've built both a design-to-Tailwind CSS+HTML converter as well as a webpage-to-Sketch Chrome extension). It is a very appealing idea - to be able to recast any webpage into a Utility CSS framework, but I'm curious to hear about situations where it would've been useful in commercial work.
But between these two exist a permutation of conditions that exists in the domain and often manifest in production. Either we can actively manage them thanks to types, or we can let it end up in production with hard-to-track bugs.