I’m also in the right side camp. It seems much easier to understand it from a high level perspective, maintain it, and test it.
Maybe the linear code is more readable, but then 3 years later you need to fix a small bug on line 242 of a 500+ line-monster-function and you typically would not spending your precious time understanding the whole function while only a small chunk is relevant.
It’s probably not easier to test, though. Pizza object is shared and mutated in multiple places. That means more of pre- and post- conditions to check.
Also, in my experience, in order to fix the issue on line 242 you need to understand the data structure setup at lines 200-240 and how is the result used on lines 260-270.
Do you know what happens in a few years when you fix the line 4 of the extracted prepare_pizza function to support triangular pizzas? That you find in production that your square boxes are not big enough to hold triangular pizzas, because the code that prepares the pizza is now disconnected from the code that boxes pizzas.
I always asked myself, ever since i got introduced to prolog at the early stages of my university module theoretical computer science and abstract datatypes - what would i use prolog for and why would i use it for that?
A way I’ve been thinking about it is what if my database was more powerful & less boilerplate.
A big one for me is why can’t I extend a table with a view in the same way prolog can share the same name for a fact & a predicate/rule. Querying prolog doesn’t care about if what im querying comes from a fact (table) or a predicate (view).
This in practice i think would enable a lot of apps to move application logic into the database, I think this is a great thing.
move application logic into the database, I think this is a great thing
The industry as a whole disagrees with this vehemently. I’m not sure if you were around for the early days of RDBMS stored procedure hell, but there’s a reason they’re used fairly infrequently.
We actually do stored procedures at work & test them via rspec but it sucks. Versioning them also sucks to deal with. And the language is terrible from most perspectives, i think primarily it sucks going to a LSP-less experience.
I think to me though the root suckiness is trying to put a procedural language side by side a declarative one.
This wasn’t what I was saying with views.
I do think databases could be more debuggable & prolog helps here because you can actually debug your queries with breakpoints and everything. Wish i could do that with sql.
EDIT: but we continue to use stored procedures (and expand on them) because its just so much faster performance-wise than doing it in rails, and I don’t think any server language could compete with doing analysis right where the data lives.
Stored procedures can absolutely be the correct approach for performance critical things (network traversal is sometimes too much), but it also really depends. It’s harder to scale a database horizontally, and every stored procedure eats CPU cycles and RAM on your DB host.
I agree, prolog != SQL and can be really nice which may address many of the issues with traditional RDBMS stored procedures.
I do think databases could be more debuggable & prolog helps here because you can actually debug your queries with breakpoints and everything. Wish i could do that with sql.
Yeah. DBs typically have pretty horrible debugging experiences, sadly.
I feel that that this is a very US-coastal point of view, like one that is common at coastal start-ups and FAANG companies but not as common elsewhere. I agree with it for the most part, but I suspect there are lots of boring enterprise companies, hospitals, and universities, running SQL Server / mostly on Windows or Oracle stacks that use the stored procedure hell pattern. I would venture that most companies that have a job title called “DBA” use this to some extent. In any case I think it’s far from the industry as a whole
Nah, I started my career out at a teleco in the Midwest, this is not a SV-centric opinion, those companies just have shit practices. Stored procedures are fine in moderation and in the right place, but pushing more of your application into the DB is very widely considered an anti-pattern and has been for at least a decade.
To be clear, I’m not saying using stored procedures at all is bad, the issue is implementing stuff that’s really data-centric application logic in your database is not great. To be fair to GP, they were talking about addressing some of the things that make approaching thing that way suck
The industry as a whole disagrees with this vehemently. I’m not sure if you were around for the early days of RDBMS stored procedure hell, but there’s a reason they’re used fairly infrequently.
… in the same way prolog can share the same name for a fact & a predicate/rule. Querying prolog doesn’t care about if what im querying comes from a fact (table) or a predicate (view).
somewhat narrowly.
Sure we do not want stored procs, but
moving Query complexity to a database (whether it is an in-process-embedded database, or external database) is a good thing.
Queries should not be implemented manually using some form of a ‘fluent’ APIs written by hand. This is like writing assembler by hand, when optimizing compilers exists and work correctly.
These kinds of query-by-hand implementations within an app, often lack global optimization opportunities (for both query and data storage).
If these by-hand implementations do include global optimizations for space and time - then they are complex, and require maintenance by specialized engineers (and that increases overall engineering costs, and may make existing system more brittle than needed).
Also, we should be using in-process databases if the data is rather static, and does not need to be distributed to other processes (this is well served by embedding prolog)
Finally, prolog-based query also includes defining ‘fitment tests’ declaratively. Then prolog query responds finding existing data items that ‘fits’ the particular fitment tests. And that’s a very valuable type of query for applications that need to check for ‘existence’ of data satisfying a set of, often complex, criteria.
Databases can also be more difficult to scale horizontally. It can also be more expensive if you’re paying to license the database software (which is relatively common). I once had the brilliant idea to implement an API as an in-process extension to the DB we were using. It was elegant, but the performance was “meh” under load, and scaling was more difficult since the whole DB had to be distributed.
I know two use cases where prolog is used earnestly, both deprecated these days:
Gerrit Code Review allowed creating new criteria a change must fulfill before it can be submitted. Examples
SPARK, an Ada dialect, used prolog up to SPARK2005 (paper). Out of the formal verification annotations and the Ada code it created prolog facts and rules. With those, certain queries passed if (and only if) the requirements encoded in those annotations were satisfied. They since moved to third party SAT solvers, which allowed them to increase the subset of Ada that could be verified (at the cost of being probabilistic, given that SAT is NP-complete: a true statement might not be verified successfully, but a false statement never passes as true), so prolog is gone.
Datalog, which is essentially a restricted Prolog, has made a bit of a resurgence in program verification. There are some new engines like Soufflé designed specifically by/for that community. Not an example of Prolog per se, but related to your point 2.
I have a slightly different question: does anybody use prolog for personal computing or scripts? I like learning languages which I can spin up to calculate something or do a 20 line script. Raku, J, and Frink are in this category for me, all as different kinds of “supercalculators”. Are there one-off things that are really easy in Prolog?
I’d say anything that solves “problems” like Sudoku or these logic puzzles I don’t know the name of “Amy lives in the red house, Peter lives next to Grace, Grace is amy’s grandma, the green house is on the left, who killed the mayor?” (OK, I made the last one up).
When I planned my wedding I briefly thought about writing some Prolog to give me a list of who should sit at which table (i.e. here’s a group of 3, a group of 5, a group of 7 and the table sizes are X,Y,Z), but in the end I did it with a piece of paper and bruteforcing by hand.
I think it would work well for class schedules, I remember one teacher at my high school had a huge whiteboard with magnets and rumour was he locked himself in for a week before each school year and crafted the schedules alone :P
The “classical” examples in my Prolog course at uni were mostly genealogy and word stems (this was in computer linguistics), but I’m not sure if that would still make sense 20y later (I had a feeling in this particular course they were a bit behind the time even in the early 00s).
I’d be interested to see a comparison like this. I don’t really know z3, but my impression is that you typically call it as a library from a more general-purpose language like Python. So I imagine you have to be aware of how there are two separate languages: z3 values are different than Python native values, and some operations like if/and/or are inappropriate to use on z3 values because they’re not fully overloadable. (Maybe similar to this style of query builder.)
By contrast, the CLP(Z) solver in Prolog feels very native. You can write some code thinking “this is a function on concrete numbers”, and use all the normal control-flow features like conditionals, or maplist. You’re thinking about numbers, not logic variables. But then it works seamlessly when you ask questions like “for which inputs is the output zero?”.
It’s really good for parsing thanks to backtracking. When you have configuration and need to check constraints on it, logic programming is the right tool. Much of classical AI is searching state spaces, and Prolog is truly excellent for that. Plus Prolog’s predicates are symmetric as opposed to functions, which are one way, so you can run them backwards to generate examples (though SMT solvers are probably a better choice for that today).
That subjectively resembles parser combinator libraries. I guess if you parse with a general-purpose language, even if the structure of your program resembles the structure of your sentences, you give up on getting anything for free; it’s impossible for a machine to say “why” an arbitrary program failed to give the result you wanted.
You can insert cuts to prevent backtracking past a certain point and keep a list of the longest successful parse to get some error information, but getting information about why the parse failed is hard.
I have used it to prototype solutions when writing code for things that don’t do a lot of I/O. I have a bunch of things and I want a bunch of other things but I’m unsure of how to go from one to the other.
In those situations it’s sometimes surprisingly easy to write the intermediary transformations in Prolog and once that works figure out “how it did it” so it can be implemented in another language.
Porting the solution to another language often takes multiple times longer than the initial Prolog implementation – so it is really powerful.
You could use it to define permissions. Imagine you have a web app with all kinds of rules like:
students can see their own grades in all courses
instructors and TAs can see all students’ grades in that course
people can’t grade each other in the same semester (or form grading cycles)
You can write down each rule once as a Prolog rule, and then query it in different ways:
What grades can Alice see?
Who can see Bob’s grade for course 123, Fall 2020?
Like a database, it will use a different execution strategy depending on the query. And also like a database, you can separately create indexes or provide hints, without changing the business logic.
For a real-world example, the Yarn package manager uses Tau Prolog–I think to let package authors define which version combinations are allowed.
When you have an appreciable level of strength with Prolog, you will find it to be a nice language for modeling problems and thinking about potential solutions. Because it lets you express ideas in a very high level, “I don’t really care how you make this happen but just do it” way, you can spend more of your time thinking about the nature of the model.
There are probably other systems that are even better at this (Alloy, for instance) but Prolog has the benefit of being extremely simple. Most of the difficulty with Prolog is in understanding this.
That hasn’t been my experience (I have written a non-trivial amount of Prolog, but not for a long time). Everything I’ve written in Prolog beyond toy examples has required me to understand how SLD derivation works and structure my code (often with red cuts) to ensure that SLD derivation reaches my goal.
This is part of the reason that Z3 is now my go-to tool for the kinds of problems where I used to use Prolog. It will use a bunch of heuristics to find a solution and has a tactics interface that lets my guide its exploration if that fails.
I don’t want to denigrate you, but in my experience, the appearance of red cuts indicates deeper problems with the model.
I’m really curious if you can point me to a largish Prolog codebase that doesn’t use red cuts. I always considered them unavoidable (which is why they’re usually introduced so early in teaching Prolog). Anything that needs a breadth-first traversal, which (in my somewhat limited experience) tends to be most things that aren’t simple data models, requires red cuts.
Unfortunately, I can’t point you to a largish Prolog codebase at all, let alone one that meets certain criteria. However, I would encourage you to follow up on this idea at https://swi-prolog.discourse.group/ since someone there may be able to present a more subtle and informed viewpoint than I can on this subject.
I will point out that the tutorial under discussion, The Power of Prolog, has almost nothing to say about cuts; searching, I only found any mention of red cuts on this page: https://www.metalevel.at/prolog/fun, where Markus is basically arguing against using them.
Because it lets you express ideas in a very high level, “I don’t really care how you make this happen but just do it” way, you can spend more of your time thinking about the nature of the model.
So when does this happen? I’ve tried to learn Prolog a few times and I guess I always managed to pick problems which Prolog’s solver sucks at solving. And figuring out how to trick Prolog’s backtracking into behaving like a better algorithm is beyond me. I think the last attempt involved some silly logic puzzle that was really easy to solve on paper; my Prolog solution took so long to run that I wrote and ran a bruteforce search over the input space in Python in the time, and gave up on the Prolog. I can’t find my code or remember what the puzzle was, annoyingly.
I am skeptical, generally, because in my view the set of search problems that are canonically solved with unguided backtracking is basically just the set of unsolved search problems. But I’d be very happy to see some satisfying examples of Prolog delivering on the “I don’t really care how you make this happen” thing.
How is that strange? It verifies that the bytecode in a function is safe to run and won’t underflow or overflow the stack or do other illegal things.
This was very important for the first use case of Java, namely untrusted applets downloaded and run in a browser. It’s still pretty advanced compared to the way JavaScript is loaded today.
I mean I can’t know from the description that it’s definitely wrong, but it sure sounds weird. Taking it away would obviously be bad, but that just moves the weirdness: why is it necessary? “Give the attacker a bunch of dangerous primitives and then check to make sure they don’t abuse them” seems like a bad idea to me. Sort of the opposite of “parse, don’t verify”.
Presumably JVMs as originally conceived verified the bytecode coming in and then blindly executed it with a VM in C or C++. Do they still work that way? I can see why the verifier would make sense in that world, although I’m still not convinced it’s a good design.
You can download a random class file from the internet and load it dynamically and have it linked together with your existing code. You somehow have to make sure it is actually type safe, and there are also in-method requirements that have to be followed (that also be type safe, plus you can’t just do pop pop pop on an empty stack). It is definitely a good design because if you prove it beforehand, then you don’t have to add runtime checks for these things.
And, depending on what you mean by “do they still work that way”, yeah, there is still byte code verification on class load, though it may be disabled for some part of the standard library by default in an upcoming release, from what I heard. You can also manually disable it if you want, but it is not recommended. But the most often ran code will execute as native machine code, so there the JIT compiler is responsible for outputting correct code.
As for the prolog part, I was wrong, it is only used in the specification, not for the actual implementation.
You can download a random class file from the internet and load it dynamically and have it linked together with your existing code. You somehow have to make sure it is actually type safe, and there are also in-method requirements that have to be followed (that also be type safe, plus you can’t just do pop pop pop on an empty stack). It is definitely a good design because if you prove it beforehand, then you don’t have to add runtime checks for these things.
I think the design problem lies in the requirements you’re taking for granted. I’m not suggesting that just yeeting some untrusted IR into memory and executing it blindly would be a good idea. Rather I think that if that’s a thing you could do, you probably weren’t going to build a secure system. For example, why are we linking code from different trust domains?
Checking untrusted bytecode to see if it has anything nasty in it has the same vibe as checking form inputs to see if they have SQL injection attacks in them. This vibe, to be precise.
…Reading this reply back I feel like I’ve made it sound like a bigger deal than it is. I wouldn’t assume a thing was inherently terrible just because it had a bytecode verifier. I just think it’s a small sign that something may be wrong.
Honestly, I can’t really think of a different way, especially regarding type checking across boundaries. You have a square-shaped hole and you want to be able to plug there squares, but you may have gotten them from any place. There is no going around checking if random thing fits a square, parsing doesn’t apply here.
Also, plain Java byte code can’t do any harm, besides crashing itself, so it is not really the case you point at — a memory-safe JVM interpreter will be memory-safe. The security issue comes from all the capabilities that JVM code can access. If anything, this type checking across boundaries is important to allow interoperability of code, and it is a thoroughly under-appreciated part of the JVM I would say: there is not many platforms that allow linking together binaries type-safely and backwards compatibly (you can extend one and it will still work fine).
Honestly, I can’t really think of a different way, especially regarding type checking across boundaries. You have a square-shaped hole and you want to be able to plug there squares, but you may have gotten them from any place. There is no going around checking if random thing fits a square, parsing doesn’t apply here.
Also, plain Java byte code can’t do any harm, besides crashing itself, so it is not really the case you point at — a memory-safe JVM interpreter will be memory-safe. The security issue comes from all the capabilities that JVM code can access. If anything, this type checking across boundaries is important to allow interoperability of code, and it is a thoroughly under-appreciated part of the JVM I would say: there is not many platforms that allow linking together binaries type-safely and backwards compatibly (you can extend one and it will still work fine).
Well, how is this different from downloading and running JS? In both cases it’s untrusted code and you put measures in place to keep it from doing unsafe things. The JS parser checks for syntax errors; the JVM verifier checks for bytecode errors.
JVMs never “blindly executed” downloaded code. That’s what SecurityManagers are for. The verifier is to ensure the bytecode doesn’t break the interpreter; the security manager prevents the code from calling unsafe APIs. (Dang, I think SecurityManager might be the wrong name. It’s been soooo long since I worked on Apple’s JVM.)
I know there have been plenty of exploits from SecurityManager bugs; I don’t remember any being caused by the bytecode verifier, which is a pretty simple/straightforward theorem prover.
In my experience, it happens when I have built up enough infrastructure around the model that I can express myself declaratively rather than procedurally. Jumping to solving the problem tends to lead to frustration; it’s better to think about different ways of representing the problem and what sorts of queries are enabled or frustrated by those approaches for a while.
Let me stress that I think of it as a tool for thinking about a problem rather than for solving a problem. Once you have a concrete idea of how to solve a problem in mind—and if you are trying to trick it into being more efficient, you are already there—it is usually more convenient to express that in another language. It’s not a tool I use daily. I don’t have brand new problems every day, unfortunately.
Some logic puzzles lend themselves to pure Prolog, but many benefit from CLP or CHR. With logic puzzles specifically, it’s good to look at some example solutions to get the spirit of how to solve them with Prolog. Knowing what to model and what to omit is a bit of an art there. I don’t usually find the best solutions to these things on my own. Also, it takes some time to find the right balance of declarative and procedural thinking when using Prolog.
Separately, being frustrated at Prolog for being weird and gassy was part of the learning experience for me. I suppose there may have been a time and place when learning it was easier than the alternatives. But it is definitely easier to learn Python or any number of modern procedural languages, and the benefit seems to be greater due to wider applicability. I am glad I know Prolog and I am happy to see people learning it. But it’s not the best tool for any job today really—but an interesting and poorly-understood tool nonetheless.
I have an unexplored idea somewhere of using it to drive the logic engine behind an always on “terraform like” controller.
Instead of defining only the state you want, it allows you to define “what actions to do to get there”, rules of what is not allowed as intermediary or final states and even ordering.
Datalog is used for querying some databases (datomic, logica, xtdb). I think the main advantages claimed over SQL are that its simple to learn and write, composable, and some claims about more efficient joins which I’m skeptical about.
Over time I have found fakes to be significantly better than mocks for most testing. They allow refactoring internal code without failing all the tests, and you can always simulate an error path by overriding a specific method in the fake.
A fake is a full implementation that takes shortcuts in its implementation. For example, a fake database could store the data in-memory and not guarantee transactions to be isolated. But it would accept inserts, updates and deletes like a real database.
A mock is a barebones implementation that confirms the methods were called in specific order and with specific values, but without understanding these values.
For example, I used to have a MockLogger, it would implement the .info, .warning and .error as jest.fn() functions, and then I have to check that the functions were called with the appropriate values. But now I have a FakeLogger that implements the methods by saving the logged messages to an array, and then I check that the array contains the messages I want.
If the people you’re working with tell you there’s “too much magic” there’s too much magic. It’s a subjective position, and the other people on your team are the ones who get to decide. Stop trying to show how clever you are and write code people can read.
The opposite also exists. If you are told to write it in a for(…;…;…) loop so that others understand it and you think there are better ways to do it, it’s fine to judge that the team needs some extra maturity.
map, filter and reduce have been existing for a long long time. Using them instead of for loops that construct new arrays is not clever code.
The maturity of the team is not a variable that can be influenced by anyone who is writing code. At least, not in any relevant timeframe. You have to write code to the level of the team you have, not the team you wish you had. Anything else is negligence.
edit: To a point, of course. And I guess that point is largely a function of language idioms, i.e. the language should be setting the expectations. If some keyword or technique is ubiquitous in the language, then it’s appropriate; if it’s a feature offered by a third-party library then you gotta get consensus.
Stop trying to show how clever you are and write code people can read.
I think that is a pretty pessimistic interpretation of why people might write code in a particular style. (Though I don’t doubt that’s why some people might do it.) But I think most of the time it’s because they got excited about something and that distinction is important.
For example, the way you are going to respond to someone who is trying to show off or lord over others is going to be different than someone who is expressing a genuine interest.
If someone on your team is taking an interest in something new it might be because they are bored. If you treat them like they are being a jerk then you are only going to make them feel worse. Instead, it’s better to redirect that energy. Maybe they need a more challenging project or they should work with an adjacent team.
That said, someone who is excited about something new might go off the deep end if they are given a lot of freedom and a blank canvas so it’s important to help guide them in constructive and practical directions. Acknowledging interests instead of shunning them can open up space for constructive criticism.
Overall, it’s important to be kind and try to work together.
That said, someone who is excited about something new might go off the deep end if they are given a lot of freedom and a blank canvas so it’s important to help guide them in constructive and practical directions.
Exactly - someone might be so excited about the new possibilities of coding in a certain way they forget about readability in their excitement to golf it down to the most elegant point-free style. But that same programmer, after a few months looking back at their own code might cringe in horror at what they’ve wrought. If even the original author later might not understand what they wrote, it’s a terrible idea to just merge it in even if the other teammembers are total blockheads (which is highly unlikely given the type of work we do), and the tech lead would be in their rights to require modifications to make it more easy to understand.
Yes, but are there any programming languages that make structures like this easy to work with?
For example if you have “at least one” and (T, List) and you want to insert into the front of the list it is a bit awkward. I think you could probably make a generic type that made this easy to work with (or even adding a min parameter to existing list types) but I wouldn’t want to pass around funny tuples.
We use the NonEmpty type in Haskell all the time, it has mostly the same interface as list. IMO the basic list of structures you need for 99% of cases representing relationships between data are:
a
Maybe a
[a]
NonEmpty a
Beyond that is quite rare, and at that point you should really be using custom types that only allow the valid states.
I’m aware there are a ton of alternative implementations here – I chose it as an example because it’s easy to understand, with a little more complexity than “fizzbuzz.” If it helps, imagine it’s actually weather simulations.
This is exactly what I’m thinking. You are constructing finite lists, so construct the list then operate on it.
let hailstone_count = hailstone(n).length
let hailstone_max = hailstone(n).fold(max)
If you cannot construct the whole list, or reconstruct it, then deal with it as a streaming problem and just combine a bunch of traversals on the stream.
With this your hailstone function doesn’t have to know something about how is it going to be consumed, and therefore is simpler and more generic.
Coding interviews are necessary because there are too many non-coders out there pretending to be coders. You need to make the candidates write code.
Having said that, 1-hour coding interviews in the style of “show me that you can fetch and show some data from a public API” is probably the right size, as it shows enough of the day to day practices. Anything beyond that (especially take-home exercises) is stretching it.
Coding interviews are necessary because there are too many non-coders out there pretending to be coders.
I have run many, many interviews at multiple companies. I have yet to encounter the mythical “non-coder” trying to trick the company into hiring them. As far as I can tell, the whole idea is based on a literal vicious cycle where anyone who fails an interview is deemed to be utterly unable to do any coding, and that’s used as justification for ratcheting up the difficulty level, which results in more people failing and being deemed unable to code, which justifies ratcheting up the difficulty…
As you can see, roughly 25% of interviewees are consistent in their performance, but the rest are all over the place. And over a third of people with a high mean (>=3) technical performance bombed at least one interview.
In the interviews they didn’t do well in, they probably were labeled as unqualified, incapable, etc. – but in the rest of their interviewers they were top performers.
I have run many, many interviews at multiple companies. I have yet to encounter the mythical “non-coder” trying to trick the company into hiring them.
Lucky you. I haven’t run that many interviews, and so far I can remember a few cases:
Junior finishing degree from degree mill, trying to pass a 5 million banking system as his own. Upon further investigation, his contribution to the code base was around 20 changes to README files.
HR woman wanting to join because HR is so easy she expects to pick programming by just working with us.
Applicant-provided code looks good. But the code in the on-site interview is very different (way lower quality). Upon further investigation, turns out the provided sample was written by her boss, not actally by the applicant.
20 years of experience on CV. Can only do what the IDE offers as buttons/dropdowns. Unable to debug the SOAP authentication in their company API because there is no button for it.
Sure, some of these can work as programmers if you are willing to lower enough the requirements. But without a technical interview where they get to write some code, or explain some code they wrote in the past, you wouln’t find out.
And yes, I also have hired people without degrees that were able to demo me something they built. I had to teach them later working in teams, agile, git, github and other stuff, but they still did good nodejs and mongo.
It’s very hard to completely bomb an interview if you can write some code. You can write wrong abstractions, bad syntax, no tests, and that is still so much better than not being able to open an IDE, or not being able to open a terminal and type ls.
have hired people without degrees that were able to demo me something they built. I had to teach them later working in teams, agile, git, github and other stuff,
This doesn’t seem degree related, since degree work won’t teach you working in teams, agile, git, or github
I have run many, many interviews at multiple companies. I have yet to encounter the mythical “non-coder” trying to trick the company into hiring them.
I’ve seen it happen; I’ve worked with someone who managed to work at a company for several years, even though it was an open secret that they couldn’t code without pairing (he was tolerated because he was likeable, and took care of a lot of the non-programming related chores)
I’ve also seen people strut into an interview with great confidence, only to write code that didn’t remotely resemble the syntax of the language they were supposedly proficient in. If this was due to anxiety, they certainly didn’t show it.*)
I don’t think it’s common enough to warrant all the anxiety about “fake programmers”, but it’s not completely imaginary.
*) I live in a country where software shops are among the few white-collar employers that’ll happily accept people who don’t speak the native language. That might mean we get more applicants for whom software development wasn’t exactly their life’s calling.
I don’t know how the situation arose, but by the time I got there, he was already known as the guy who did the “chores”, and avoided pairing to - presumably - reduce the chance of being found out. This all changed when new management came in, which implemented some - very overdue - changes.
Maybe the takeaway should be that there might be room for “non-coders” in development teams, but the situation as it was can’t have been easy on anybody.
Coding interviews are necessary because there are too many non-coders out there pretending to be coders. You need to make the candidates write code.
I can’t speak for all jurisdictions, but this feels a little overblown to me, at least in the Australian market.
If someone manages to sneak through and it turns out they have no experience and can’t do the job, you can dismiss them within the 6 month probation period.
Yes, you would have likely wasted some time and money, but it shouldn’t be significant if this is a disproportionately small number of candidates.
The thing is the culture in the US is quite different- some companies will fire people willy nilly, which is bad for morale, and other companies are very hesitant to fire anyone for incompetence, because by firing someone you leave them and their family without access to health care (and it’s bad for morale). Either way, you do actually want to make good decisions most of the time when hiring.
I am OK with CS 101 DS and algo type of questions. The ones I am afraid the most are with additional “real-world” knowledge: design an efficient shuffling deck of cards, OOP design of parking garage, meeting time scheduling. I’m not familiar with playing card games. I don’t drive a car and have no idea wtf how a parking garage operates.
Fizzbuzz is pretty good. The only issue I’ve encountered is not knowing or not remembering what modulo is. I certainly didn’t know the first time I was asked to implement it.
When I was 17, I did very badly in a Cambridge undergraduate admissions interview. The interviewer asked me a simple question with an obvious answer. Cambridge interviews are notoriously hard and so I assumed that I was missing something important and stalled for a long time while I tried to work out what the trick part of the question was. Eventually they gave up and told me the answer. It was precisely the one that I’d thought of immediately but since I’d been primed to expect a difficult question, I couldn’t believe that this was really what they were asking. I think I used somewhere between a quarter and a half of the total interview time failing to answer a question that they were expecting would take 30 seconds. I did not get an offer.
Questions like Fizzbuzz worry me because I’d expect them to trigger the same response from a lot of people. Fizzbuzz, in particular, has a very large solution space. A trivial implementation might look something like this:
for (int i=1 ; i<max ; i++)
{
if ((i % 3) == 0)
printf("fizz\n");
else if ((i % 5) == 0)
printf("buzz\n");
else if (((i %3) == 0) && ((i %5) == 0)
printf("fizzbuzz\n");
else
printf("%d\n", i);
}
You could remove one of the branches by allowing fall-through into the buzz case even after you’ve done fizz, but then you need to worry about atomicity of the writes. Is that something the interview is expecting the candidate to think about? Are they expecting the candidate to think about the problem a bit more and realise that the pattern repeats 15 times (because 3 and 5 are both prime and 15 is 3x5 and therefore the lowest common multiple)? If so, the solution should look something like this, which avoids any modulo arithmetic:
for (int i=1 ; i<max ; i+=15)
{
printf("%d\n%d\nfizz\n%d\nbuzz\nfizz\n%d\n%d\nfizz\nbuzz\n%d\nfizz\n%d\n%d\nfizzbuzz\n"
i, i+1, i+3, i+6, i+7, i+10, i+12, 1+13);
}
With some special-case logic at the end for an incomplete sequence. Of course, printf is a very expensive call. You could optimise this by computing it once for each digit length and then just doing decimal arithmetic in the string.
And so on. There is a lot more optimisation that you can do with fizzbuzz. Knowing what the interviewer expects from the first attempt may not be obvious.
Except it’s not necessary… it’s not particularly hard to do the arithmetic to figure divisibility or keep a couple extra loop counters and solve it without knowing/using the operator while still not introducing any new concepts to the solution. You might have to give a candidate doing that a bit more time, perhaps. But if anything, seeing that solution and then offering an explanation of modulo w/ a chance to revise is likely to be an even better assessment.
Name popular OSS software, written in Haskell, not used for Haskell management (e.g. Cabal).
AFAICT, there are only two, pandoc and XMonad.
This does not strike me as being an unreasonably effective language. There are tons of tools written in Rust you can name, and Rust is a significantly younger language.
People say there is a ton of good Haskell locked up in fintech, and that may be true, but a) fintech is weird because it has infinite money and b) there are plenty of other languages used in fintech which are also popular outside of it, eg Python, so it doesn’t strike me as being a good counterexample, even if we grant that it is true.
I think postgrest is a great idea, but it can be applied to very wrong situations. Unless you’re familiar with Postgres, you might be surprised with how much application logic can be modelled purely in the database without turning it into spaghetti. At that point, you can make the strategic choice of modelling a part of your domain purely in the DB and let the clients work directly with it.
To put it differently, postgrest is an architectural tool, it can be useful for giving front-end teams a fast path to maintaining their own CRUD stores and endpoints. You can still have other parts of the database behind your API.
I don’t understand Postgrest. IMO, the entire point of an API is to provide an interface to the database and explicitly decouple the internals of the database from the rest of the world. If you change the schema, all of your Postgrest users break. API is an abstraction layer serving exactly what the application needs and nothing more. It provides a way to maintain backwards compatibility if you need. You might as well just send sql query to a POST endpoint and eliminate the need for Postgrest - not condoning it but saying how silly the idea of postgrest is.
Sometimes you just don’t want to make any backend application, only to have a web frontend talk to a database. There are whole “as-a-Service” products like Firebase that offer this as part of their functionality. Postgrest is self-hosted that. It’s far more convenient than sending bare SQL directly.
with views, one can largely get around the break the schema break the API problem. Even so, as long as the consumers of the API are internal, you control both ends, so it’s pretty easy to just schedule your cutovers.
But I think the best use-case for Postgrest is old stable databases that aren’t really changing stuff much anymore but need to add a fancy web UI.
The database people spend 10 minutes turning up Postgrest and leave the UI people to do their thing and otherwise ignore them.
Hah, I don’t get views either. My philosophy is that the database is there to store the data. It is the last thing that scales. Don’t put logic and abstraction layers in the database. There is plenty of compute available outside of it and APIs can do precise data abstraction needed for the apps. Materialized views, may be, but still feels wrong. SQL is a pain to write tests for.
Your perspective is certainly a reasonable one, but not one I or many people necessarily agree with.
The more data you have to mess with, the closer you want the messing with next to the data. i.e. in the same process if possible :) Hence Pl/PGSQL and all the other languages that can get embedded into SQL databases.
Have you checked row-level security? I think it creates a good default, and then you can use security definer views for when you need to override that default.
Yes, That’s exactly how we use access control views! I’m a huge fan of RLS, so much so that all of our users get their own role in PG, and our app(s) auth directly to PG. We happily encourage direct SQL access to our users, since all of our apps use RLS for their security.
Our biggest complaint with RLS, none(?) of the reporting front ends out there have any concept of RLS or really DB security in general, they AT BEST offer some minimal app-level security that’s usually pretty annoying. I’ve never been upset enough to write one…yet, but I hope someone someday does.
That’s exactly how we use access control views! I’m a huge fan of RLS, so much so that all of our users get their own role in PG
When each user has it its own role, usually that means ‘Role explosion’ [1].
But perhaps you have other methods/systems that let you avoid that.
How do you do for example: user ‘X’ when operating at location “Poland” is not allowed to access Report data ‘ABC’ before 8am and after 4pm UTC-2, in Postgres ?
Well in PG a role IS a user, there is no difference, but I agree that RBAC is not ideal when your user count gets high as management can be complicated. Luckily our database includes all the HR data, so we know this person is employed with this job on these dates, etc. We utilize that information in our, mostly automated, user controls and accounts. When one is a supervisor, they have the permission(s) given to them, and they can hand them out like candy to their employees, all within our UI.
We try to model the UI around “capabilities”, all though it’s implemented through RBAC obviously, and is not a capability based system.
So each supervisor is responsible for their employees permissions, and we largely try to stay out of it. They can’t define the “capabilities”, that’s on us.
How do you do for example: user ‘X’ when operating at location “Poland” is not allowed to access Report data ‘ABC’ before 8am and after 4pm UTC-2, in Postgres ?
Unfortunately PG’s RBAC doesn’t really allow us to do that easily, and we luckily haven’t yet had a need to do something that detailed. It is possible, albeit non-trivial. We try to limit our access rules to more basic stuff: supervisor(s) can see/update data within their sphere but not outside of it, etc.
We do limit users based on their work location, but not their logged in location. We do log all activity in an audit log, which is just another DB table, and it’s in the UI for everyone with the right permissions(so a supervisor can see all their employee’s activity, whenever they want).
Certainly different authorization system(s) exist, and they all have their pros and cons, but we’ve so far been pretty happy with PG’s system. If you can write a query to generate the data needed to make a decision, then you can make the system authorize with it.
My philosophy is “don’t write half-baked abstractions again and again”. PostgREST & friends (like Postgraphile) provide selecting specific columns, joins, sorting, filtering, pagination and others. I’m tired of writing that again and again for each endpoint, except each endpoint is slightly different, as it supports sorting on different fields, or different styles of filtering. PostgREST does all of that once and for all.
Also, there are ways to test SQL, and databases supporting transaction isolation actually simplify running your tests. Just wrap your test in a BEGIN; ROLLBACK; block.
Idk, I’ve been bitten by this. Probably ok in a small project, but this is a dangerous tight coupling of the entire system. Next time a new requirement comes in that requires changing the schema, RIP, wouldn’t even know which services would break and how many things would go wrong. Write fully-baked, well tested, requirements contested, exceptionally vetted, and excellently thought out abstractions.
I’m a fan of tools that support incremental refactoring and decomposition of a program’s architecture w/o major API breakage. PostgREST feels to me like a useful tool in that toolbox, especially when coupled with procedural logic in the database. Plus there’s the added bonus of exposing the existing domain model “natively” as JSON over HTTP, which is one of the rare integration models better supported than even the native PG wire protocol.
With embedded subresources and full SQL view support you can quickly get to something that’s as straightforward for a FE project to talk to as a bespoke REST or GraphQL backend.. Keeping the schema definitions in one place (i.e., the database itself) means less mirroring of the same structures and serialization approaches in multiple tiers of my application.
I’m building a project right now where PostgREST fills the same architectural slot that a Django or Laravel application might, but without having to build and maintain that service at all. Will I eventually need to split the API so I can add logic that doesn’t map to tuples and functions on them? Sure, maybe, if the app gets traction at all. Does it help me keep my tiers separate for now while I’m working solo on a project that might naturally decompose into a handful of backend services and an integration layer? Yep, also working out thus far.
There are some things that strike me as awkward and/or likely to cause problems down the road, like pushing JWT handling down into the DB itself. I also think it’s a weird oversight to not expose LISTEN/NOTIFY over websockets or SSE, given that PostgREST already uses notification channels to handle its schema cache refresh trigger.
Again, though, being able to wire a hybrid SPA/SSG framework like SvelteKit into a “native” database backend without having to deploy a custom API layer has been a nice option for rapid prototyping and even “real” CRUD applications. As a bonus, my backend code can just talk to Postgres directly, which means I can use my preferred stack there (Rust + SQLx + Warp) without doing yet another intermediate JSON (un)wrap step. Eventually – again, modulo actually needing the app to work for more than a few months – more and more will migrate into that service, but in the meantime I can keep using fetch in my frontend and move on.
I think it’s true that, historically, Haskell hasn’t been used as much for open source work as you might expect given the quality of the language. I think there are a few factors that are in play here, but the dominant one is simply that the open source projects that take off tend to be ones that a lot of people are interested in and/or contribute to. Haskell has, historically, struggled with a steep on-ramp and that means that the people who persevered and learned the language well enough to build things with it were self-selected to be the sorts of people who were highly motivated to work on Haskell and it’s ecosystem, but it was less appealing if your goals were to do something else and get that done quickly. It’s rare for Haskell to be the only language that someone knows, so even among Haskell developers I think it’s been common to pick a different language if the goal is to get a lot of community involvement in a project.
All that said, I think things are shifting. The Haskell community is starting to think earnestly about broadening adoption and making the language more appealing to a wider variety of developers. There are a lot of problems where Haskell makes a lot of sense, and we just need to see the friction for picking it reduced in order for the adoption to pick up. In that sense, the fact that many other languages are starting to add some things that are heavily inspired by Haskell makes Haskell itself more appealing, because more of the language is going to look familiar and that’s going to make it more accessible to people.
There are tons of tools written in Rust you can name
I can’t think of anything off the dome except ripgrep. I’m sure I could do some research and find a few, but I’m sure that’s also the case for Haskell.
You’ve probably heard of Firefox and maybe also Deno. When you look through the GitHub Rust repos by stars, there are a bunch of ls clones weirdly, lol.
Agree … and finance and functional languages seem to have a connection empirically:
OCaml and Jane St (they strongly advocate it, mostly rejecting polyglot approaches, doing almost everything within OCaml)
the South American bank that bought the company behind Clojure
I think it’s obviously the domain … there is simple a lot of “purely functional” logic in finance.
Implementing languages and particularly compilers is another place where that’s true, which the blog post mentions. But I’d say that isn’t true for most domains.
BTW git annex appears to be written in Haskell. However my experience with it is mixed. It feels like git itself is more reliable and it’s written in C/Perl/Shell. I think the dominating factor is just the number and skill of developers, not the language.
OCaml also has a range of more or less (or once) popular non-fintech, non-compiler tools written in it. LiquidSoap, MLDonkey, Unison file synchronizer, 0install, the original PGP key server…
I think the connection with finance is that making mistakes in automated finance is actually very costly on expectation, whereas making mistakes in a social network or something is typically not very expensive.
Not being popular is not the same as being “ineffective”. Likewise, something can be “effective”, but not popular.
Is JavaScript a super effective language? Is C?
Without going too far down the language holy war rabbit hole, my overall feeling after so many years is that programming language popularity, in general, fits a “worse is better” characterization where the languages that I, personally, feel are the most bug-prone, poorly designed, etc, are the most popular. Nobody has to agree with me, but for the sake of transparency, I’m thinking of PHP, C, JavaScript, Python, and Java when I write that. Languages that are probably pretty good/powerful/good-at-preventing-bugs are things like Haskell, Rust, Clojure, Elixir.
In the past, a lot of the reason I’ve seen people being turned away from using Haskell based tools has been the perceived pain of installing GHC, which admittedly is quite large, and it can sometime be a pain to figure out which version you need. ghcup has improved that situation quite a lot by making the process of installing and managing old compilers significantly easier. There’s still an argument that GHC is massive, which it is, but storage is pretty cheap these days. For some reason I’ve never seen people make similar complaints about needing to install multiple version of python (though this is less off an issue these days).
The other place where large Haskell codebases are locked up is Facebook - Sigma processes every single post, comment and massage for spam, at 2,000,000 req/sec, and is all written in Haskell. Luckily the underlying tech, Haxl, is open source - though few people seem to have found a particularly good use for it, you really need to be working at quite a large scale to benefit from it.
I think this paragraph from the new foreword is relevant:
While JavaScript, like Java, was originally designed with objects and methods in mind, it also incorporated first-class functions from the beginning, and it is not difficult to use its objects to define a universal data structure. As a result, JavaScript is not as distant from Lisp as you would think, and as this edition of Structure and Interpretation of Computer Programs demonstrates, it is a good alternate framework for presenting the key ideas. SICP was never about a programming language; it presents powerful, general ideas for program organization that ought to be useful in any language.
TypeScript doesn’t fix any fundamentally wrong issues with JS (which are the worst of it from a CS rather than SE perspective); it just fixes the overall experience of writing, maintaining, and using others’ TS/JS code. That is to say, I don’t think it would have been a qualitative improvement over JS in this case, only a quantitative one.
From the list of mainstream languages, there is really not so many good candidares:
Java: class-obsessed, hard to construct functions.
Python: defs are statements, lambdas are are expressions. This is a book about CS, not programming language semantics.
C/C++: Not easy to construct anonymous functions, and then pointers.
C#: see Java, but with more lock-in.
PHP: you serious? Javascript is a perfectly designed programming language in comparison to PHP.
So at the end of the day, JS is an acceptable functional language. So why not TS? Well this is a book on general CS. If you want to talk about types, there are books way better suited for it (and in these books they laugh at TS from their dependent-types ivory towers).
If we are composing functions, what’s the type of a function that accepts two functions as parameters? Well, now we are going to waste 20 pages explaining types, TS syntax, gotchas of TS syntax, type parameters, gotchas of type parameters in TS, and so on.
Effectively, TS adds little to what the book teaches, while adding significant overhead.
There are three levels of frontend interaction complexity:
Up to form action=foo method=post and button type=submit. For this, HTML + CSS is the easiest solution.
Up to buttons with simple animations. Jquery or vanilla is the easiest solution.
Anything over it. React with potentially extra state management libraries is the easiest solution.
Yes, you don’t need React for websites with minimal interactivity requirements, but as soon as you have to add just a few of those requirements, you quickly regret not having React.
Also, things like GatsbyJS or NextJS can be configured to output static sites. This may look like totally overkill, except for a tiny detail. You get to use all the module management and import semantics from Javascript to manage your components, which is way better than plain old CSS files and copy-pasta for HTML snippets. Add in Typescript and you have a strong proposition. Though WebComponents may do something similar here…
There’s a level past React too though. React can’t really do high performance things, so for those you need to turn to Canvas or something and handroll it. Tom MacWright made this point in the now classic “Rethinking the Modern Web”.
Here’s one way to think of it:
Google search: can be handled as HTML, CSS, sprinkles of JS animation
Gmail: you’ll want some kind of JS framework to handle all the zillions of screens
Google maps: you don’t want a framework doing the map because it will be too slow. The map is canvas or some other smart layer that can load tiles on the fly. (Nav buttons can be whatever.)
It’s extremely easy to migrate from a simple HTML + CSS site to a site with JQuery or vanilla JavaScript, because JavaScript is basically just a layer over the HTML page that’s already there. React wants to have the center stage, and most knowledge about the underlying technology is useless at best and sometimes even harmful, as React wants everything done in a very specific way.
It doesn’t have to be like this. You can tell React to render on any subsection of the page, and manage the rest of the page in a different way. (This is the big secret behind all that microframeworks hipe we saw a few months ago). When you do React this way, it becomes another layer of the system. It becomes just an element with incredibly advanced functionality. And you can delete the element with node.parent.removeChild(node) and the React node goes away and the React runtime stops.
React doesn’t want to have the center stage. People put React in the center stage.
I had a few attempts to learn it, but I failed every time. It is fair to say that I don’t understand it, so I cannot even rant about its features, shortcomings, or flaws.
to be fair I’m using it for one hobby project (not a webdev at day) and still feel overwhelmed by the amount of additional plugins (Redux, how do you do global state for login + user-menu that is decoupled, hooks…). Add the whole JS stack on top (with its 40k modules) and you are starting to feel like every major rust dependency graph is irrelevant in comparison. Vue felt way easier but lacked one major component for one thing I needed (QR code display) and often it’s not obvious how you use any external JS libraries together with these self-managing frameworks.
Don’t do Redux. Only big sites warrant using it. Start with minimal hooks (useState) passing the state and setters as params. everywhere. Once you understand this, work your way up using contexts and effects. You don’t need more than this for 80% complex sites out there.
UML works for a very narrow view of software development. I found that out when I wrote my thesis in OCaml and had to document it in UML. How do you express a curried function in UML? How do you express a simple function in UML that happens to not be attached to an object?
And that is only the tip of the iceberg.
How do you express a complex SQL query in UML?
Entity-relation diagrams help modelling the schema, but what about a query with joins and window functions?
How do you express a map-reduce operation in UML?
What’s the diagram for filtering a list, then aggregating the results?
How do you express a minimally-complex algorithm in UML (think Sieve of Erastothenes)?
Why can’t I use pseudo-code, which beats diagrams every day of the week?
How do you express a reactive user interface in UML?
Why do I have to use something like a collaboration diagram, when the state of the interface derives directly the representation of the interface? It’s not the case that the input enables the submit button when it finds itself no longer empty.
How do you express a Prolog program in UML?
Do you think the UML committee has ever known about the existence of Prolog?
How do I represent a multi-param generic data structure? For example, a mapping/hash table from A to B, where A and B are arbitrary types.
And then we ignore the old problem of documentation and software diverging over time, and having to update the documentation when the software changes.
UML comes from an era where Object Oriented (Obsessed?) Design was the whole world, and everything that wasn’t OOD was being forced to not exist. In this era, Architects would draw UML diagrams and cheap Indian code monkeys would do the coding, because we thought that code monkeys would be cheap and replaceable. This was perfect for the MBAs because programmers started to be annoying by not delivering on the expected deadlines, and claiming about the algorithms, the damned algorithms. We wanted to replace them with cheaper and less-whiny ones.
Turns out that the details of the system are in the code, not in the diagrams, and only the trivial parts of the system would be expressed in diagrams, and the devil is in the details. But this makes programmers non replaceable and fungible, because the better ones can express algorithms that the worse programmers will never understand. And many times, you need these better algorithms.
All this makes UML not anymore the silver bullet for bodyshops. The broken idea of hiring 1 architect and 100 code monkeys from the consultancy just doesn’t work, because the architect is not expected to dig in the details, and the 100 code monkeys will just make a mess of the details.
UML was dead on arrival when it ignored most of the details of Software Development. Doesn’t mean though that some parts of UML can be salvaged, such as sequence diagrams, state diagrams, or entity-relation diagrams. But trying to model in UML an arbitrary problem and solution is likely to become wrestling with the language to express anything minimally complex or different.
How do you express a …
UML was dead on arrival when it ignored most of the details of Software Development.
The UML isn’t supposed to model software down to the query and the algorithm. It’s supposed to model a system and its processes, to assist with the design of a software implementation of that system.
If the way you think about modelling a system is “what complex SQL queries will I need, and how can I add a functional reactive UI” then indeed the UML will not help as the paradigm of its creators and users is the object oriented paradigm. You are thinking about the problem in a way the UML will not help you to express, so we do not expect it to help with the expression of those thoughts.
The OO paradigm of the UML isn’t the “objects are inheritance and encapsulation” straw man of blog posts about switching to functional, but the “objects are things in the problem domain reflected in software” of object oriented analysis and design.
To the extent that the UML and other tools from OOSE were “dead in the water” (an odd claim given their former prevalence), a more convincing reason is that they were sold as efficiency and productivity bolt-ons to programmers without also explaining the need for a paradigm shift. A large number of companies adopted Java, carried on writing structured software, and noticed that they were also having to do this O-O ceremony that doesn’t help them.
In a few years time they’ll notice that switching to Scala, carrying on writing structured software, and also doing this functional ceremony isn’t helping them, and we’ll all get to have this thread again on the “has currying died and nobody noticed?” posts.
The UML isn’t supposed to model software down to the query and the algorithm. It’s supposed to model a system and its processes, to assist with the design of a software implementation of that system.
Then I would like to have the class diagram removed from UML, please, because class diagrams define a lot of details of data structures, and that restricts a lot on the algorithms I’m allowed to use.
You are thinking about the problem in a way the UML will not help you to express, so we do not expect it to help with the expression of those thoughts.
And now you are agreeing with me that many problems and solutions cannot be expressed in UML unless I twist the problem/solution to fit UML.
This is not about Object Oriented Programming vs Functional Programming. This is about the fact that I can’t express many things in UML, starting with the whole universe of functional programming, and continuing with database interactions, declarative programming, advanced data structures, compositional semantics, and many others that I haven’t got time yet to study. Each of those alternative ways to do computing beat the others in specific situations, and having to use UML just forces me to not be able to use the right tool for the job because the UML committee decided that it is hammers for everyone. And now I have to make this screwdriver look like a hammer to be able to draw it in UML.
Then I would like to have the class diagram removed from UML, please, because class diagrams define a lot of details of data structures, and that restricts a lot on the algorithms I’m allowed to use.
Firstly, you’re welcome to not use class diagrams. Secondly, you’re welcome to only put the details you need into a diagram, and avoid constraining your implementation: the map is not the terrain.
And now you are agreeing with me that many problems and solutions cannot be expressed in UML unless I twist the problem/solution to fit UML.
I don’t think so. It sounds like you’re saying the UML is bad because you can’t do these things, whereas I’m saying the UML is good when I don’t do these things. “Doctor, it hurts when I lift my arm like this!”
I hate when people use this analogy. If it hurts when I lift my arm like this, that’s probably a sign of some deeper underlying problem! Don’t tell me to not lift my arm.
The OO paradigm of the UML isn’t the “objects are inheritance and encapsulation” straw man of blog posts about switching to functional
That straw man is exactly what I’ve been taught at school. Shapes and animals and all that. Sure it’s just for learning, but the next step is invariably “OO is hard”, “OO is complicated”, “OO takes times to master”… the usual conversation stoppers.
[UML is] “objects are things in the problem domain reflected in software”
That also is likely a mistake. As Mike Acton so eloquently put it, we should not code around a model of the world (in this case, the things in the problem domain). We should code around a model of the data. The things in the problem domain are what you speak of before managers or domain specialists. When you start actually programming however, it quickly becomes about the shape and size and flow of the data, and UML doesn’t help much there.
“objects are things in the problem domain reflected in software” of object oriented analysis and design.
What does that even mean ? I have never seen this aspect explained except in the most superficial terms - creating a class which has a method name that reflects something from the domain usually resulting in bikeshed arguments.
I am sorry but that is not a model of anything. It is just naming. We can’t call naming modelling. When I am making a model of a circuit in software I can interact with the model, test all the assumptions. When an actual architect uses AutoDesk they are modelling things and can interact with them. BDD does this so it can absolutely be called modelling in some sense. I don’t know if it is the best modelling technique we can come up with but it works.
I’d recommend Eric Evans’s book on domain-driven design. “Just naming” implies that you’ve already decided what the method does and that you want to find something in the problem domain to map it onto. OOA/D says that you have found something important in the problem domain and that you want your software to simulate it.
I also posted about implementing a similar component. I didn’t cover handling the error state (so I appreciate that you did), but one problem I’ve run into was that when switching between two subtrees with the data loader component at the same level, the component wouldn’t actually be unmounted, which broke a few of my assumptions. I think wrapping it in another component like you did in the final CommentsSection implementation fixes this, though.
Also somebody in the comments also pointed to the Apollo Query component, which has yet another API but essentially does the same thing.
Old components may not be unmounted because the final tree contains the same component in between state changes. This can be the case if all your sub-components are divs, for example. In that case, React will happily not rebuild the component, and instead will just change the props of it. Then you need to use the componentWillReceiveProps, that only complicates it and makes for more antipatterns and worse code.
Depending on how you can refactor it, you can trick React into rebuilding the whole component, for example by returning an array of a single component with a random key, or some kind of key that changes when you want the DOM tree to be rebuilt. It is a bit annoying, but it’s significantly less annoying than the componentWillReceiveProps route.
Then you need to use the componentWillReceiveProps, that only complicates it and makes for more antipatterns and worse code.
Yep, componentWillReceiveProps has only caused me problems. That’s why I prefer hooks in general, they make it easier to spot this kind of errors and correct them “the right way”.
I must be confused. You’re saying that every application should enumerate every potential combination of data, one-by-one? I don’t see where algebraic datatypes are being applied.
Enumerating every potential combination is obviously impractical. But thinking about it helps you design types in a better way so that you can reduce the amount of data that is valid. One of the principles I use when designing datatypes is looking for groups of data that are absolutely independent of each other, and thinking about them separately. Then, for the groups of data that are somewhat dependent on each other, there is usually one field that determines the others in some fashion, and I split the data based on this field.
The example applies the underlying ideas behind ADTs to look for the final solution. In Haskell, you could think of something equivalent in the style of:
data NoFields = NoFields
data Field1Only = Field1Only {
field1 :: String
}
data BothField1AndField2 = BothField1AndField2 {
field1 :: String,
field2 :: String
}
data InvalidObject = InvalidObject {
field2 :: String
}
data Fields
= NF NoFields
| F1O Field1Only
| BF1AF2 BothField1AndField2
-- Didn't put InvalidObject here
I think I understand. But this seems like an application of structural typing to enumerate permissible combinations of fields, rather than of algebraic datatypes.
The object may not have neither of the two fields.
The object may have only field1, and not field2.
Only if the object has field1, then it can have field2.
It seems to me that the way these are worded is rather confusing. I can figure it out eventually going through each line, and the #2 creates a contradiction with #3. It seems to me that if #3 is true, then #2 cannot be true.
I hope they add something like pattern matching to make checking through the cases of the ADT a bit less cumbersome. Given how much ceremony it takes to define and use an ADT, I sometimes wonder if I’m even doing the right thing.
The switch statement is kind of a weak pattern matching. It doesn’t allow you to match on the fields or extract field values easily. I still consider it a good tool for your toolbox, specially if you do Redux. In Redux, you have ADTs, either explicitly declared, or implicitly declared. I prefer explicit in terms of types, so at least the typechecker can spot some of my mistakes.
What I never understand with smart contracts that require off chain data:
If it requires an oracle, why not just run it off chain? In this case you’re fully trusting the exchange, so it’s not any more “trustless” than if the buyer’s wallet had an integration with ShapeShift/Changelly/some other instant exchange.
if the buyer’s wallet had an integration with ShapeShift/Changelly/some other instant exchange
What if you want to deal with the average exchange ratio? How do you convince these instant exchanges to cooperate to provide the average ratio, considering that they may not want to cooperate? On the other hand, asking each one for an oracle, and having a smart contract that accepts several oracles to compute the average is not that hard.
What if you want to deal with the average exchange ratio?
Why? The average isn’t useful (in the context of the article). If I want someone to send me $10 USD, I’d ask the exchange how much $10 USD in Bitcoin is, and ask for that amount. The average would mean that I could get more or less depending on which exchange I have an account with.
I think the idea is that you trust external parties to provide data (because it can be audited), but you don’t trust external parties to run code. I think it makes sense.
i.e. User types in their wallet app to request $10 USD. The wallet secretly makes a deal with the user’s exchange and determines that $10 USD will be worth X Coins. The wallet makes a request to the other person for X coins. Using oracles just adds extra cost for not a lot more trust.
I’m also in the right side camp. It seems much easier to understand it from a high level perspective, maintain it, and test it.
Maybe the linear code is more readable, but then 3 years later you need to fix a small bug on line 242 of a 500+ line-monster-function and you typically would not spending your precious time understanding the whole function while only a small chunk is relevant.
It’s probably not easier to test, though. Pizza object is shared and mutated in multiple places. That means more of pre- and post- conditions to check.
Also, in my experience, in order to fix the issue on line 242 you need to understand the data structure setup at lines 200-240 and how is the result used on lines 260-270.
Do you know what happens in a few years when you fix the line 4 of the extracted prepare_pizza function to support triangular pizzas? That you find in production that your square boxes are not big enough to hold triangular pizzas, because the code that prepares the pizza is now disconnected from the code that boxes pizzas.
I always asked myself, ever since i got introduced to prolog at the early stages of my university module theoretical computer science and abstract datatypes - what would i use prolog for and why would i use it for that?
A way I’ve been thinking about it is what if my database was more powerful & less boilerplate.
A big one for me is why can’t I extend a table with a view in the same way prolog can share the same name for a fact & a predicate/rule. Querying prolog doesn’t care about if what im querying comes from a fact (table) or a predicate (view).
This in practice i think would enable a lot of apps to move application logic into the database, I think this is a great thing.
The industry as a whole disagrees with this vehemently. I’m not sure if you were around for the early days of RDBMS stored procedure hell, but there’s a reason they’re used fairly infrequently.
What went wrong with them?
It’s nearly impossible to add tests of any kind to a stored procedure is the biggest one, IMO.
We actually do stored procedures at work & test them via rspec but it sucks. Versioning them also sucks to deal with. And the language is terrible from most perspectives, i think primarily it sucks going to a LSP-less experience.
I think to me though the root suckiness is trying to put a procedural language side by side a declarative one.
This wasn’t what I was saying with views.
I do think databases could be more debuggable & prolog helps here because you can actually debug your queries with breakpoints and everything. Wish i could do that with sql.
EDIT: but we continue to use stored procedures (and expand on them) because its just so much faster performance-wise than doing it in rails, and I don’t think any server language could compete with doing analysis right where the data lives.
Stored procedures can absolutely be the correct approach for performance critical things (network traversal is sometimes too much), but it also really depends. It’s harder to scale a database horizontally, and every stored procedure eats CPU cycles and RAM on your DB host.
I agree, prolog != SQL and can be really nice which may address many of the issues with traditional RDBMS stored procedures.
Yeah. DBs typically have pretty horrible debugging experiences, sadly.
I feel that that this is a very US-coastal point of view, like one that is common at coastal start-ups and FAANG companies but not as common elsewhere. I agree with it for the most part, but I suspect there are lots of boring enterprise companies, hospitals, and universities, running SQL Server / mostly on Windows or Oracle stacks that use the stored procedure hell pattern. I would venture that most companies that have a job title called “DBA” use this to some extent. In any case I think it’s far from the industry as a whole
Nah, I started my career out at a teleco in the Midwest, this is not a SV-centric opinion, those companies just have shit practices. Stored procedures are fine in moderation and in the right place, but pushing more of your application into the DB is very widely considered an anti-pattern and has been for at least a decade.
To be clear, I’m not saying using stored procedures at all is bad, the issue is implementing stuff that’s really data-centric application logic in your database is not great. To be fair to GP, they were talking about addressing some of the things that make approaching thing that way suck
@ngp, but I think you are interpreting
somewhat narrowly.
Sure we do not want stored procs, but moving Query complexity to a database (whether it is an in-process-embedded database, or external database) is a good thing.
Queries should not be implemented manually using some form of a ‘fluent’ APIs written by hand. This is like writing assembler by hand, when optimizing compilers exists and work correctly.
These kinds of query-by-hand implementations within an app, often lack global optimization opportunities (for both query and data storage). If these by-hand implementations do include global optimizations for space and time - then they are complex, and require maintenance by specialized engineers (and that increases overall engineering costs, and may make existing system more brittle than needed).
Also, we should be using in-process databases if the data is rather static, and does not need to be distributed to other processes (this is well served by embedding prolog)
Finally, prolog-based query also includes defining ‘fitment tests’ declaratively. Then prolog query responds finding existing data items that ‘fits’ the particular fitment tests. And that’s a very valuable type of query for applications that need to check for ‘existence’ of data satisfying a set of, often complex, criteria.
Databases can also be more difficult to scale horizontally. It can also be more expensive if you’re paying to license the database software (which is relatively common). I once had the brilliant idea to implement an API as an in-process extension to the DB we were using. It was elegant, but the performance was “meh” under load, and scaling was more difficult since the whole DB had to be distributed.
I know two use cases where prolog is used earnestly, both deprecated these days:
Gerrit Code Review allowed creating new criteria a change must fulfill before it can be submitted. Examples
SPARK, an Ada dialect, used prolog up to SPARK2005 (paper). Out of the formal verification annotations and the Ada code it created prolog facts and rules. With those, certain queries passed if (and only if) the requirements encoded in those annotations were satisfied. They since moved to third party SAT solvers, which allowed them to increase the subset of Ada that could be verified (at the cost of being probabilistic, given that SAT is NP-complete: a true statement might not be verified successfully, but a false statement never passes as true), so prolog is gone.
Datalog, which is essentially a restricted Prolog, has made a bit of a resurgence in program verification. There are some new engines like Soufflé designed specifically by/for that community. Not an example of Prolog per se, but related to your point 2.
Yes, Datalog is heavily used for building state-of-the-art static analyzers, see https://arxiv.org/abs/2012.10086.
This is a great comment, I never knew about the Gerrit code review criteria.
I have a slightly different question: does anybody use prolog for personal computing or scripts? I like learning languages which I can spin up to calculate something or do a 20 line script. Raku, J, and Frink are in this category for me, all as different kinds of “supercalculators”. Are there one-off things that are really easy in Prolog?
I’d say anything that solves “problems” like Sudoku or these logic puzzles I don’t know the name of “Amy lives in the red house, Peter lives next to Grace, Grace is amy’s grandma, the green house is on the left, who killed the mayor?” (OK, I made the last one up).
When I planned my wedding I briefly thought about writing some Prolog to give me a list of who should sit at which table (i.e. here’s a group of 3, a group of 5, a group of 7 and the table sizes are X,Y,Z), but in the end I did it with a piece of paper and bruteforcing by hand.
I think it would work well for class schedules, I remember one teacher at my high school had a huge whiteboard with magnets and rumour was he locked himself in for a week before each school year and crafted the schedules alone :P
The “classical” examples in my Prolog course at uni were mostly genealogy and word stems (this was in computer linguistics), but I’m not sure if that would still make sense 20y later (I had a feeling in this particular course they were a bit behind the time even in the early 00s).
This class of problems has a few different names: https://en.wikipedia.org/wiki/Zebra_Puzzle
Wonder if it would be good for small but intricate scheduling problems, like vacation planning. I’d compare with minizinc and z3.
I’d be interested to see a comparison like this. I don’t really know z3, but my impression is that you typically call it as a library from a more general-purpose language like Python. So I imagine you have to be aware of how there are two separate languages: z3 values are different than Python native values, and some operations like
if/and/or
are inappropriate to use on z3 values because they’re not fully overloadable. (Maybe similar to this style of query builder.)By contrast, the CLP(Z) solver in Prolog feels very native. You can write some code thinking “this is a function on concrete numbers”, and use all the normal control-flow features like conditionals, or maplist. You’re thinking about numbers, not logic variables. But then it works seamlessly when you ask questions like “for which inputs is the output zero?”.
It’s really good for parsing thanks to backtracking. When you have configuration and need to check constraints on it, logic programming is the right tool. Much of classical AI is searching state spaces, and Prolog is truly excellent for that. Plus Prolog’s predicates are symmetric as opposed to functions, which are one way, so you can run them backwards to generate examples (though SMT solvers are probably a better choice for that today).
Prolog is both awesome and terrible for parsing.
Awesome: DCGs + backtracking are a killer combo
Terrible: If it fails to parse, you get a “No”, and nothing more. No indication of the row, col, falied token, nothing.
That subjectively resembles parser combinator libraries. I guess if you parse with a general-purpose language, even if the structure of your program resembles the structure of your sentences, you give up on getting anything for free; it’s impossible for a machine to say “why” an arbitrary program failed to give the result you wanted.
You can insert cuts to prevent backtracking past a certain point and keep a list of the longest successful parse to get some error information, but getting information about why the parse failed is hard.
And then your cuts are in the way for using the parser as a generator, thus killing the DCG second use.
I have used it to prototype solutions when writing code for things that don’t do a lot of I/O. I have a bunch of things and I want a bunch of other things but I’m unsure of how to go from one to the other.
In those situations it’s sometimes surprisingly easy to write the intermediary transformations in Prolog and once that works figure out “how it did it” so it can be implemented in another language.
Porting the solution to another language often takes multiple times longer than the initial Prolog implementation – so it is really powerful.
You could use it to define permissions. Imagine you have a web app with all kinds of rules like:
You can write down each rule once as a Prolog rule, and then query it in different ways:
Like a database, it will use a different execution strategy depending on the query. And also like a database, you can separately create indexes or provide hints, without changing the business logic.
For a real-world example, the Yarn package manager uses Tau Prolog–I think to let package authors define which version combinations are allowed.
When you have an appreciable level of strength with Prolog, you will find it to be a nice language for modeling problems and thinking about potential solutions. Because it lets you express ideas in a very high level, “I don’t really care how you make this happen but just do it” way, you can spend more of your time thinking about the nature of the model.
There are probably other systems that are even better at this (Alloy, for instance) but Prolog has the benefit of being extremely simple. Most of the difficulty with Prolog is in understanding this.
That hasn’t been my experience (I have written a non-trivial amount of Prolog, but not for a long time). Everything I’ve written in Prolog beyond toy examples has required me to understand how SLD derivation works and structure my code (often with red cuts) to ensure that SLD derivation reaches my goal.
This is part of the reason that Z3 is now my go-to tool for the kinds of problems where I used to use Prolog. It will use a bunch of heuristics to find a solution and has a tactics interface that lets my guide its exploration if that fails.
I don’t want to denigrate you, but in my experience, the appearance of red cuts indicates deeper problems with the model.
I’m glad you found a tool that works for you in Z3, and I am encouraged by your comment about it to check it out soon. Thank you!
I’m really curious if you can point me to a largish Prolog codebase that doesn’t use red cuts. I always considered them unavoidable (which is why they’re usually introduced so early in teaching Prolog). Anything that needs a breadth-first traversal, which (in my somewhat limited experience) tends to be most things that aren’t simple data models, requires red cuts.
Unfortunately, I can’t point you to a largish Prolog codebase at all, let alone one that meets certain criteria. However, I would encourage you to follow up on this idea at https://swi-prolog.discourse.group/ since someone there may be able to present a more subtle and informed viewpoint than I can on this subject.
I will point out that the tutorial under discussion, The Power of Prolog, has almost nothing to say about cuts; searching, I only found any mention of red cuts on this page: https://www.metalevel.at/prolog/fun, where Markus is basically arguing against using them.
So when does this happen? I’ve tried to learn Prolog a few times and I guess I always managed to pick problems which Prolog’s solver sucks at solving. And figuring out how to trick Prolog’s backtracking into behaving like a better algorithm is beyond me. I think the last attempt involved some silly logic puzzle that was really easy to solve on paper; my Prolog solution took so long to run that I wrote and ran a bruteforce search over the input space in Python in the time, and gave up on the Prolog. I can’t find my code or remember what the puzzle was, annoyingly.
I am skeptical, generally, because in my view the set of search problems that are canonically solved with unguided backtracking is basically just the set of unsolved search problems. But I’d be very happy to see some satisfying examples of Prolog delivering on the “I don’t really care how you make this happen” thing.
As an example, I believe Java’s class loader verifier is written in prolog (even the specification is written in a prolog-ish way).
class loader verifier? What a strange thing to even exist… but thanks, I’ll have a look.
How is that strange? It verifies that the bytecode in a function is safe to run and won’t underflow or overflow the stack or do other illegal things.
This was very important for the first use case of Java, namely untrusted applets downloaded and run in a browser. It’s still pretty advanced compared to the way JavaScript is loaded today.
I mean I can’t know from the description that it’s definitely wrong, but it sure sounds weird. Taking it away would obviously be bad, but that just moves the weirdness: why is it necessary? “Give the attacker a bunch of dangerous primitives and then check to make sure they don’t abuse them” seems like a bad idea to me. Sort of the opposite of “parse, don’t verify”.
Presumably JVMs as originally conceived verified the bytecode coming in and then blindly executed it with a VM in C or C++. Do they still work that way? I can see why the verifier would make sense in that world, although I’m still not convinced it’s a good design.
You can download a random class file from the internet and load it dynamically and have it linked together with your existing code. You somehow have to make sure it is actually type safe, and there are also in-method requirements that have to be followed (that also be type safe, plus you can’t just do pop pop pop on an empty stack). It is definitely a good design because if you prove it beforehand, then you don’t have to add runtime checks for these things.
And, depending on what you mean by “do they still work that way”, yeah, there is still byte code verification on class load, though it may be disabled for some part of the standard library by default in an upcoming release, from what I heard. You can also manually disable it if you want, but it is not recommended. But the most often ran code will execute as native machine code, so there the JIT compiler is responsible for outputting correct code.
As for the prolog part, I was wrong, it is only used in the specification, not for the actual implementation.
I think the design problem lies in the requirements you’re taking for granted. I’m not suggesting that just yeeting some untrusted IR into memory and executing it blindly would be a good idea. Rather I think that if that’s a thing you could do, you probably weren’t going to build a secure system. For example, why are we linking code from different trust domains?
Checking untrusted bytecode to see if it has anything nasty in it has the same vibe as checking form inputs to see if they have SQL injection attacks in them. This vibe, to be precise.
…Reading this reply back I feel like I’ve made it sound like a bigger deal than it is. I wouldn’t assume a thing was inherently terrible just because it had a bytecode verifier. I just think it’s a small sign that something may be wrong.
Honestly, I can’t really think of a different way, especially regarding type checking across boundaries. You have a square-shaped hole and you want to be able to plug there squares, but you may have gotten them from any place. There is no going around checking if random thing fits a square, parsing doesn’t apply here.
Also, plain Java byte code can’t do any harm, besides crashing itself, so it is not really the case you point at — a memory-safe JVM interpreter will be memory-safe. The security issue comes from all the capabilities that JVM code can access. If anything, this type checking across boundaries is important to allow interoperability of code, and it is a thoroughly under-appreciated part of the JVM I would say: there is not many platforms that allow linking together binaries type-safely and backwards compatibly (you can extend one and it will still work fine).
Honestly, I can’t really think of a different way, especially regarding type checking across boundaries. You have a square-shaped hole and you want to be able to plug there squares, but you may have gotten them from any place. There is no going around checking if random thing fits a square, parsing doesn’t apply here.
Also, plain Java byte code can’t do any harm, besides crashing itself, so it is not really the case you point at — a memory-safe JVM interpreter will be memory-safe. The security issue comes from all the capabilities that JVM code can access. If anything, this type checking across boundaries is important to allow interoperability of code, and it is a thoroughly under-appreciated part of the JVM I would say: there is not many platforms that allow linking together binaries type-safely and backwards compatibly (you can extend one and it will still work fine).
Well, how is this different from downloading and running JS? In both cases it’s untrusted code and you put measures in place to keep it from doing unsafe things. The JS parser checks for syntax errors; the JVM verifier checks for bytecode errors.
JVMs never “blindly executed” downloaded code. That’s what SecurityManagers are for. The verifier is to ensure the bytecode doesn’t break the interpreter; the security manager prevents the code from calling unsafe APIs. (Dang, I think SecurityManager might be the wrong name. It’s been soooo long since I worked on Apple’s JVM.)
I know there have been plenty of exploits from SecurityManager bugs; I don’t remember any being caused by the bytecode verifier, which is a pretty simple/straightforward theorem prover.
In my experience, it happens when I have built up enough infrastructure around the model that I can express myself declaratively rather than procedurally. Jumping to solving the problem tends to lead to frustration; it’s better to think about different ways of representing the problem and what sorts of queries are enabled or frustrated by those approaches for a while.
Let me stress that I think of it as a tool for thinking about a problem rather than for solving a problem. Once you have a concrete idea of how to solve a problem in mind—and if you are trying to trick it into being more efficient, you are already there—it is usually more convenient to express that in another language. It’s not a tool I use daily. I don’t have brand new problems every day, unfortunately.
Some logic puzzles lend themselves to pure Prolog, but many benefit from CLP or CHR. With logic puzzles specifically, it’s good to look at some example solutions to get the spirit of how to solve them with Prolog. Knowing what to model and what to omit is a bit of an art there. I don’t usually find the best solutions to these things on my own. Also, it takes some time to find the right balance of declarative and procedural thinking when using Prolog.
Separately, being frustrated at Prolog for being weird and gassy was part of the learning experience for me. I suppose there may have been a time and place when learning it was easier than the alternatives. But it is definitely easier to learn Python or any number of modern procedural languages, and the benefit seems to be greater due to wider applicability. I am glad I know Prolog and I am happy to see people learning it. But it’s not the best tool for any job today really—but an interesting and poorly-understood tool nonetheless.
I have an unexplored idea somewhere of using it to drive the logic engine behind an always on “terraform like” controller.
Instead of defining only the state you want, it allows you to define “what actions to do to get there”, rules of what is not allowed as intermediary or final states and even ordering.
All things that terraform makes hard rn.
Datalog is used for querying some databases (datomic, logica, xtdb). I think the main advantages claimed over SQL are that its simple to learn and write, composable, and some claims about more efficient joins which I’m skeptical about.
https://docs.datomic.com/pro/query/query.html#why-datalog has some justifications of their choice of datalog.
Datomic and XTDB see some real world use as application databases for clojure apps. Idk if anyone uses logica.
Over time I have found fakes to be significantly better than mocks for most testing. They allow refactoring internal code without failing all the tests, and you can always simulate an error path by overriding a specific method in the fake.
What’s the difference between a mock and a fake?
A fake is a full implementation that takes shortcuts in its implementation. For example, a fake database could store the data in-memory and not guarantee transactions to be isolated. But it would accept inserts, updates and deletes like a real database.
A mock is a barebones implementation that confirms the methods were called in specific order and with specific values, but without understanding these values.
For example, I used to have a MockLogger, it would implement the .info, .warning and .error as jest.fn() functions, and then I have to check that the functions were called with the appropriate values. But now I have a FakeLogger that implements the methods by saving the logged messages to an array, and then I check that the array contains the messages I want.
https://martinfowler.com/bliki/TestDouble.html though it is fairly common to talk of Mocks when a different kind of Test Double is meant
This got me curious. Does V8 or other JS engine optimize .map calls with fusion?
I don’t think so because it would mess up the ordering of side effects.
No. Stream fusion is still a pretty unique capability of Haskell.
My thought exactly. Without some form of lazyness, this code is really suspicious to begin with
If the people you’re working with tell you there’s “too much magic” there’s too much magic. It’s a subjective position, and the other people on your team are the ones who get to decide. Stop trying to show how clever you are and write code people can read.
The opposite also exists. If you are told to write it in a for(…;…;…) loop so that others understand it and you think there are better ways to do it, it’s fine to judge that the team needs some extra maturity.
map, filter and reduce have been existing for a long long time. Using them instead of for loops that construct new arrays is not clever code.
The maturity of the team is not a variable that can be influenced by anyone who is writing code. At least, not in any relevant timeframe. You have to write code to the level of the team you have, not the team you wish you had. Anything else is negligence.
edit: To a point, of course. And I guess that point is largely a function of language idioms, i.e. the language should be setting the expectations. If some keyword or technique is ubiquitous in the language, then it’s appropriate; if it’s a feature offered by a third-party library then you gotta get consensus.
I think that is a pretty pessimistic interpretation of why people might write code in a particular style. (Though I don’t doubt that’s why some people might do it.) But I think most of the time it’s because they got excited about something and that distinction is important.
For example, the way you are going to respond to someone who is trying to show off or lord over others is going to be different than someone who is expressing a genuine interest.
If someone on your team is taking an interest in something new it might be because they are bored. If you treat them like they are being a jerk then you are only going to make them feel worse. Instead, it’s better to redirect that energy. Maybe they need a more challenging project or they should work with an adjacent team.
That said, someone who is excited about something new might go off the deep end if they are given a lot of freedom and a blank canvas so it’s important to help guide them in constructive and practical directions. Acknowledging interests instead of shunning them can open up space for constructive criticism.
Overall, it’s important to be kind and try to work together.
Exactly - someone might be so excited about the new possibilities of coding in a certain way they forget about readability in their excitement to golf it down to the most elegant point-free style. But that same programmer, after a few months looking back at their own code might cringe in horror at what they’ve wrought. If even the original author later might not understand what they wrote, it’s a terrible idea to just merge it in even if the other teammembers are total blockheads (which is highly unlikely given the type of work we do), and the tech lead would be in their rights to require modifications to make it more easy to understand.
Really looking forward to the JSON Schema writeup now that you’ve had this experience!
Ninja edit: Do you know about Spectral as well?
I haven’t heard of Spectral! Very cool.
JSON Schema seems so verbose, at first look.
JSON Schema pays with verbosity being able to be serialized, stored and sent over the network. The advantages of being JSON.
BTW I can’t recommend JSON schema enough.
Multiplicities are hard if you don’t study your theory.
And now you combine them with tuples.
Of course, you can throw dependant types and interfaces to it to make it nicer.
Laurence Tratt has been publishing PLT papers for almost two decades, I think he knows the theory.
Yes, but are there any programming languages that make structures like this easy to work with?
For example if you have “at least one” and
(T, List)
and you want to insert into the front of the list it is a bit awkward. I think you could probably make a generic type that made this easy to work with (or even adding a min parameter to existing list types) but I wouldn’t want to pass around funny tuples.We use the NonEmpty type in Haskell all the time, it has mostly the same interface as list. IMO the basic list of structures you need for 99% of cases representing relationships between data are:
Beyond that is quite rare, and at that point you should really be using custom types that only allow the valid states.
Hm, I’d say the example is more specific: it is “internal iteration”, rather than general strategy. I usually code those as
It probably is important to note, for pedagogical purposes, that in this case external iteration works fine:
I’m aware there are a ton of alternative implementations here – I chose it as an example because it’s easy to understand, with a little more complexity than “fizzbuzz.” If it helps, imagine it’s actually weather simulations.
This is exactly what I’m thinking. You are constructing finite lists, so construct the list then operate on it.
If you cannot construct the whole list, or reconstruct it, then deal with it as a streaming problem and just combine a bunch of traversals on the stream.
With this your hailstone function doesn’t have to know something about how is it going to be consumed, and therefore is simpler and more generic.
I don’t thin finitness plays a role here.
iter::successors
would work just fine for an infinite list as well.Coding interviews are necessary because there are too many non-coders out there pretending to be coders. You need to make the candidates write code.
Having said that, 1-hour coding interviews in the style of “show me that you can fetch and show some data from a public API” is probably the right size, as it shows enough of the day to day practices. Anything beyond that (especially take-home exercises) is stretching it.
I have run many, many interviews at multiple companies. I have yet to encounter the mythical “non-coder” trying to trick the company into hiring them. As far as I can tell, the whole idea is based on a literal vicious cycle where anyone who fails an interview is deemed to be utterly unable to do any coding, and that’s used as justification for ratcheting up the difficulty level, which results in more people failing and being deemed unable to code, which justifies ratcheting up the difficulty…
And that’s not just my personal opinion/anecdote: this online interviewing platform has a decent sample size and says:
In the interviews they didn’t do well in, they probably were labeled as unqualified, incapable, etc. – but in the rest of their interviewers they were top performers.
Lucky you. I haven’t run that many interviews, and so far I can remember a few cases:
Sure, some of these can work as programmers if you are willing to lower enough the requirements. But without a technical interview where they get to write some code, or explain some code they wrote in the past, you wouln’t find out.
And yes, I also have hired people without degrees that were able to demo me something they built. I had to teach them later working in teams, agile, git, github and other stuff, but they still did good nodejs and mongo.
It’s very hard to completely bomb an interview if you can write some code. You can write wrong abstractions, bad syntax, no tests, and that is still so much better than not being able to open an IDE, or not being able to open a terminal and type
ls
.This doesn’t seem degree related, since degree work won’t teach you working in teams, agile, git, or github
I’ve seen it happen; I’ve worked with someone who managed to work at a company for several years, even though it was an open secret that they couldn’t code without pairing (he was tolerated because he was likeable, and took care of a lot of the non-programming related chores)
I’ve also seen people strut into an interview with great confidence, only to write code that didn’t remotely resemble the syntax of the language they were supposedly proficient in. If this was due to anxiety, they certainly didn’t show it.*)
I don’t think it’s common enough to warrant all the anxiety about “fake programmers”, but it’s not completely imaginary.
*) I live in a country where software shops are among the few white-collar employers that’ll happily accept people who don’t speak the native language. That might mean we get more applicants for whom software development wasn’t exactly their life’s calling.
I would hope that after years of pairing they started being able to code… Otherwise I blame their pairing partners.
I don’t know how the situation arose, but by the time I got there, he was already known as the guy who did the “chores”, and avoided pairing to - presumably - reduce the chance of being found out. This all changed when new management came in, which implemented some - very overdue - changes.
Maybe the takeaway should be that there might be room for “non-coders” in development teams, but the situation as it was can’t have been easy on anybody.
I can’t speak for all jurisdictions, but this feels a little overblown to me, at least in the Australian market.
If someone manages to sneak through and it turns out they have no experience and can’t do the job, you can dismiss them within the 6 month probation period.
Yes, you would have likely wasted some time and money, but it shouldn’t be significant if this is a disproportionately small number of candidates.
The thing is the culture in the US is quite different- some companies will fire people willy nilly, which is bad for morale, and other companies are very hesitant to fire anyone for incompetence, because by firing someone you leave them and their family without access to health care (and it’s bad for morale). Either way, you do actually want to make good decisions most of the time when hiring.
I am OK with CS 101 DS and algo type of questions. The ones I am afraid the most are with additional “real-world” knowledge: design an efficient shuffling deck of cards, OOP design of parking garage, meeting time scheduling. I’m not familiar with playing card games. I don’t drive a car and have no idea wtf how a parking garage operates.
Make them do a fizzbuzz. That’s like algorithms 101, yet it reveals so much on the programmer:
Fizzbuzz is pretty good. The only issue I’ve encountered is not knowing or not remembering what modulo is. I certainly didn’t know the first time I was asked to implement it.
When I was 17, I did very badly in a Cambridge undergraduate admissions interview. The interviewer asked me a simple question with an obvious answer. Cambridge interviews are notoriously hard and so I assumed that I was missing something important and stalled for a long time while I tried to work out what the trick part of the question was. Eventually they gave up and told me the answer. It was precisely the one that I’d thought of immediately but since I’d been primed to expect a difficult question, I couldn’t believe that this was really what they were asking. I think I used somewhere between a quarter and a half of the total interview time failing to answer a question that they were expecting would take 30 seconds. I did not get an offer.
Questions like Fizzbuzz worry me because I’d expect them to trigger the same response from a lot of people. Fizzbuzz, in particular, has a very large solution space. A trivial implementation might look something like this:
You could remove one of the branches by allowing fall-through into the buzz case even after you’ve done fizz, but then you need to worry about atomicity of the writes. Is that something the interview is expecting the candidate to think about? Are they expecting the candidate to think about the problem a bit more and realise that the pattern repeats 15 times (because 3 and 5 are both prime and 15 is 3x5 and therefore the lowest common multiple)? If so, the solution should look something like this, which avoids any modulo arithmetic:
With some special-case logic at the end for an incomplete sequence. Of course,
printf
is a very expensive call. You could optimise this by computing it once for each digit length and then just doing decimal arithmetic in the string.And so on. There is a lot more optimisation that you can do with fizzbuzz. Knowing what the interviewer expects from the first attempt may not be obvious.
Except it’s not necessary… it’s not particularly hard to do the arithmetic to figure divisibility or keep a couple extra loop counters and solve it without knowing/using the operator while still not introducing any new concepts to the solution. You might have to give a candidate doing that a bit more time, perhaps. But if anything, seeing that solution and then offering an explanation of modulo w/ a chance to revise is likely to be an even better assessment.
Name popular OSS software, written in Haskell, not used for Haskell management (e.g. Cabal).
AFAICT, there are only two, pandoc and XMonad.
This does not strike me as being an unreasonably effective language. There are tons of tools written in Rust you can name, and Rust is a significantly younger language.
People say there is a ton of good Haskell locked up in fintech, and that may be true, but a) fintech is weird because it has infinite money and b) there are plenty of other languages used in fintech which are also popular outside of it, eg Python, so it doesn’t strike me as being a good counterexample, even if we grant that it is true.
Here’s a Github search: https://github.com/search?l=&o=desc&q=stars%3A%3E500+language%3AHaskell&s=stars&type=Repositories
I missed a couple of good ones:
Still, compare this to any similarly old and popular language, and it’s no contest.
Also Dhall
I think postgrest is a great idea, but it can be applied to very wrong situations. Unless you’re familiar with Postgres, you might be surprised with how much application logic can be modelled purely in the database without turning it into spaghetti. At that point, you can make the strategic choice of modelling a part of your domain purely in the DB and let the clients work directly with it.
To put it differently, postgrest is an architectural tool, it can be useful for giving front-end teams a fast path to maintaining their own CRUD stores and endpoints. You can still have other parts of the database behind your API.
I don’t understand Postgrest. IMO, the entire point of an API is to provide an interface to the database and explicitly decouple the internals of the database from the rest of the world. If you change the schema, all of your Postgrest users break. API is an abstraction layer serving exactly what the application needs and nothing more. It provides a way to maintain backwards compatibility if you need. You might as well just send sql query to a POST endpoint and eliminate the need for Postgrest - not condoning it but saying how silly the idea of postgrest is.
Sometimes you just don’t want to make any backend application, only to have a web frontend talk to a database. There are whole “as-a-Service” products like Firebase that offer this as part of their functionality. Postgrest is self-hosted that. It’s far more convenient than sending bare SQL directly.
with views, one can largely get around the break the schema break the API problem. Even so, as long as the consumers of the API are internal, you control both ends, so it’s pretty easy to just schedule your cutovers.
But I think the best use-case for Postgrest is old stable databases that aren’t really changing stuff much anymore but need to add a fancy web UI.
The database people spend 10 minutes turning up Postgrest and leave the UI people to do their thing and otherwise ignore them.
Hah, I don’t get views either. My philosophy is that the database is there to store the data. It is the last thing that scales. Don’t put logic and abstraction layers in the database. There is plenty of compute available outside of it and APIs can do precise data abstraction needed for the apps. Materialized views, may be, but still feels wrong. SQL is a pain to write tests for.
Your perspective is certainly a reasonable one, but not one I or many people necessarily agree with.
The more data you have to mess with, the closer you want the messing with next to the data. i.e. in the same process if possible :) Hence Pl/PGSQL and all the other languages that can get embedded into SQL databases.
We use views mostly for 2 reasons:
Have you checked row-level security? I think it creates a good default, and then you can use security definer views for when you need to override that default.
Yes, That’s exactly how we use access control views! I’m a huge fan of RLS, so much so that all of our users get their own role in PG, and our app(s) auth directly to PG. We happily encourage direct SQL access to our users, since all of our apps use RLS for their security.
Our biggest complaint with RLS, none(?) of the reporting front ends out there have any concept of RLS or really DB security in general, they AT BEST offer some minimal app-level security that’s usually pretty annoying. I’ve never been upset enough to write one…yet, but I hope someone someday does.
When each user has it its own role, usually that means ‘Role explosion’ [1]. But perhaps you have other methods/systems that let you avoid that.
How do you do for example: user ‘X’ when operating at location “Poland” is not allowed to access Report data ‘ABC’ before 8am and after 4pm UTC-2, in Postgres ?
[1] https://blog.plainid.com/role-explosion-unintended-consequence-rbac
Well in PG a role IS a user, there is no difference, but I agree that RBAC is not ideal when your user count gets high as management can be complicated. Luckily our database includes all the HR data, so we know this person is employed with this job on these dates, etc. We utilize that information in our, mostly automated, user controls and accounts. When one is a supervisor, they have the permission(s) given to them, and they can hand them out like candy to their employees, all within our UI.
We try to model the UI around “capabilities”, all though it’s implemented through RBAC obviously, and is not a capability based system.
So each supervisor is responsible for their employees permissions, and we largely try to stay out of it. They can’t define the “capabilities”, that’s on us.
Unfortunately PG’s RBAC doesn’t really allow us to do that easily, and we luckily haven’t yet had a need to do something that detailed. It is possible, albeit non-trivial. We try to limit our access rules to more basic stuff: supervisor(s) can see/update data within their sphere but not outside of it, etc.
We do limit users based on their work location, but not their logged in location. We do log all activity in an audit log, which is just another DB table, and it’s in the UI for everyone with the right permissions(so a supervisor can see all their employee’s activity, whenever they want).
Certainly different authorization system(s) exist, and they all have their pros and cons, but we’ve so far been pretty happy with PG’s system. If you can write a query to generate the data needed to make a decision, then you can make the system authorize with it.
My philosophy is “don’t write half-baked abstractions again and again”. PostgREST & friends (like Postgraphile) provide selecting specific columns, joins, sorting, filtering, pagination and others. I’m tired of writing that again and again for each endpoint, except each endpoint is slightly different, as it supports sorting on different fields, or different styles of filtering. PostgREST does all of that once and for all.
Also, there are ways to test SQL, and databases supporting transaction isolation actually simplify running your tests. Just wrap your test in a BEGIN; ROLLBACK; block.
Idk, I’ve been bitten by this. Probably ok in a small project, but this is a dangerous tight coupling of the entire system. Next time a new requirement comes in that requires changing the schema, RIP, wouldn’t even know which services would break and how many things would go wrong. Write fully-baked, well tested, requirements contested, exceptionally vetted, and excellently thought out abstractions.
Or just use views to maintain backwards compatibility and generate typings from the introspection endpoint to typecheck clients.
I’m a fan of tools that support incremental refactoring and decomposition of a program’s architecture w/o major API breakage. PostgREST feels to me like a useful tool in that toolbox, especially when coupled with procedural logic in the database. Plus there’s the added bonus of exposing the existing domain model “natively” as JSON over HTTP, which is one of the rare integration models better supported than even the native PG wire protocol.
With embedded subresources and full SQL view support you can quickly get to something that’s as straightforward for a FE project to talk to as a bespoke REST or GraphQL backend.. Keeping the schema definitions in one place (i.e., the database itself) means less mirroring of the same structures and serialization approaches in multiple tiers of my application.
I’m building a project right now where PostgREST fills the same architectural slot that a Django or Laravel application might, but without having to build and maintain that service at all. Will I eventually need to split the API so I can add logic that doesn’t map to tuples and functions on them? Sure, maybe, if the app gets traction at all. Does it help me keep my tiers separate for now while I’m working solo on a project that might naturally decompose into a handful of backend services and an integration layer? Yep, also working out thus far.
There are some things that strike me as awkward and/or likely to cause problems down the road, like pushing JWT handling down into the DB itself. I also think it’s a weird oversight to not expose LISTEN/NOTIFY over websockets or SSE, given that PostgREST already uses notification channels to handle its schema cache refresh trigger.
Again, though, being able to wire a hybrid SPA/SSG framework like SvelteKit into a “native” database backend without having to deploy a custom API layer has been a nice option for rapid prototyping and even “real” CRUD applications. As a bonus, my backend code can just talk to Postgres directly, which means I can use my preferred stack there (Rust + SQLx + Warp) without doing yet another intermediate JSON (un)wrap step. Eventually – again, modulo actually needing the app to work for more than a few months – more and more will migrate into that service, but in the meantime I can keep using
fetch
in my frontend and move on.I would add shake
https://shakebuild.com
not exactly a tool but a great DSL.
I think it’s true that, historically, Haskell hasn’t been used as much for open source work as you might expect given the quality of the language. I think there are a few factors that are in play here, but the dominant one is simply that the open source projects that take off tend to be ones that a lot of people are interested in and/or contribute to. Haskell has, historically, struggled with a steep on-ramp and that means that the people who persevered and learned the language well enough to build things with it were self-selected to be the sorts of people who were highly motivated to work on Haskell and it’s ecosystem, but it was less appealing if your goals were to do something else and get that done quickly. It’s rare for Haskell to be the only language that someone knows, so even among Haskell developers I think it’s been common to pick a different language if the goal is to get a lot of community involvement in a project.
All that said, I think things are shifting. The Haskell community is starting to think earnestly about broadening adoption and making the language more appealing to a wider variety of developers. There are a lot of problems where Haskell makes a lot of sense, and we just need to see the friction for picking it reduced in order for the adoption to pick up. In that sense, the fact that many other languages are starting to add some things that are heavily inspired by Haskell makes Haskell itself more appealing, because more of the language is going to look familiar and that’s going to make it more accessible to people.
I can’t think of anything off the dome except ripgrep. I’m sure I could do some research and find a few, but I’m sure that’s also the case for Haskell.
You’ve probably heard of Firefox and maybe also Deno. When you look through the GitHub Rust repos by stars, there are a bunch of ls clones weirdly, lol.
Agree … and finance and functional languages seem to have a connection empirically:
I think it’s obviously the domain … there is simple a lot of “purely functional” logic in finance.
Implementing languages and particularly compilers is another place where that’s true, which the blog post mentions. But I’d say that isn’t true for most domains.
BTW git annex appears to be written in Haskell. However my experience with it is mixed. It feels like git itself is more reliable and it’s written in C/Perl/Shell. I think the dominating factor is just the number and skill of developers, not the language.
OCaml also has a range of more or less (or once) popular non-fintech, non-compiler tools written in it. LiquidSoap, MLDonkey, Unison file synchronizer, 0install, the original PGP key server…
Xen hypervisor
The MirageOS project always seemed super cool. Unikernels are very interesting.
Well, the tools for it, rather than the hypervisor itself. But yeah, I forgot about that one.
I think the connection with finance is that making mistakes in automated finance is actually very costly on expectation, whereas making mistakes in a social network or something is typically not very expensive.
Git-annex
Not being popular is not the same as being “ineffective”. Likewise, something can be “effective”, but not popular.
Is JavaScript a super effective language? Is C?
Without going too far down the language holy war rabbit hole, my overall feeling after so many years is that programming language popularity, in general, fits a “worse is better” characterization where the languages that I, personally, feel are the most bug-prone, poorly designed, etc, are the most popular. Nobody has to agree with me, but for the sake of transparency, I’m thinking of PHP, C, JavaScript, Python, and Java when I write that. Languages that are probably pretty good/powerful/good-at-preventing-bugs are things like Haskell, Rust, Clojure, Elixir.
In the past, a lot of the reason I’ve seen people being turned away from using Haskell based tools has been the perceived pain of installing GHC, which admittedly is quite large, and it can sometime be a pain to figure out which version you need.
ghcup
has improved that situation quite a lot by making the process of installing and managing old compilers significantly easier. There’s still an argument that GHC is massive, which it is, but storage is pretty cheap these days. For some reason I’ve never seen people make similar complaints about needing to install multiple version of python (though this is less off an issue these days).The other place where large Haskell codebases are locked up is Facebook - Sigma processes every single post, comment and massage for spam, at 2,000,000 req/sec, and is all written in Haskell. Luckily the underlying tech, Haxl, is open source - though few people seem to have found a particularly good use for it, you really need to be working at quite a large scale to benefit from it.
hledger is one I use regularly.
Cardano is a great example.
Or Standard Chartered, which is a very prominent British bank, and runs all their backend on Haskell. They even have their own strict dialect.
GHC.
https://pandoc.org/
I used pandoc for a long time before even realizing it was Haskell. Ended up learning just enough to make a change I needed.
I think this paragraph from the new foreword is relevant:
There are also the usual pragmatic reasons to choose JavaScript as a teaching language.
Why would this be so terrible?
There’s a whole bunch of places to get surprised by Javascript:
https://medium.com/javascript-non-grata/javascript-is-a-dysfunctional-programming-language-a1f4866e186f
Typescript would have been a better choice.
TypeScript doesn’t fix any fundamentally wrong issues with JS (which are the worst of it from a CS rather than SE perspective); it just fixes the overall experience of writing, maintaining, and using others’ TS/JS code. That is to say, I don’t think it would have been a qualitative improvement over JS in this case, only a quantitative one.
From the list of mainstream languages, there is really not so many good candidares:
Java: class-obsessed, hard to construct functions.
Python: defs are statements, lambdas are are expressions. This is a book about CS, not programming language semantics.
C/C++: Not easy to construct anonymous functions, and then pointers.
C#: see Java, but with more lock-in.
PHP: you serious? Javascript is a perfectly designed programming language in comparison to PHP.
So at the end of the day, JS is an acceptable functional language. So why not TS? Well this is a book on general CS. If you want to talk about types, there are books way better suited for it (and in these books they laugh at TS from their dependent-types ivory towers).
If we are composing functions, what’s the type of a function that accepts two functions as parameters? Well, now we are going to waste 20 pages explaining types, TS syntax, gotchas of TS syntax, type parameters, gotchas of type parameters in TS, and so on.
Effectively, TS adds little to what the book teaches, while adding significant overhead.
There are three levels of frontend interaction complexity:
form action=foo method=post
andbutton type=submit
. For this, HTML + CSS is the easiest solution.Yes, you don’t need React for websites with minimal interactivity requirements, but as soon as you have to add just a few of those requirements, you quickly regret not having React.
Also, things like GatsbyJS or NextJS can be configured to output static sites. This may look like totally overkill, except for a tiny detail. You get to use all the module management and import semantics from Javascript to manage your components, which is way better than plain old CSS files and copy-pasta for HTML snippets. Add in Typescript and you have a strong proposition. Though WebComponents may do something similar here…
There’s a level past React too though. React can’t really do high performance things, so for those you need to turn to Canvas or something and handroll it. Tom MacWright made this point in the now classic “Rethinking the Modern Web”.
Here’s one way to think of it:
It’s extremely easy to migrate from a simple HTML + CSS site to a site with JQuery or vanilla JavaScript, because JavaScript is basically just a layer over the HTML page that’s already there. React wants to have the center stage, and most knowledge about the underlying technology is useless at best and sometimes even harmful, as React wants everything done in a very specific way.
See this line of code? https://github.com/manirul41/react-mvc-app/blob/master/src/index.js#L7 It tells React to do its thing on the id=“root” element in the page. What tends to happen is https://github.com/manirul41/react-mvc-app/blob/master/src/index.html#L15 - I/E the only element in the page is that id=“root”.
It doesn’t have to be like this. You can tell React to render on any subsection of the page, and manage the rest of the page in a different way. (This is the big secret behind all that microframeworks hipe we saw a few months ago). When you do React this way, it becomes another layer of the system. It becomes just an element with incredibly advanced functionality. And you can delete the element with node.parent.removeChild(node) and the React node goes away and the React runtime stops.
React doesn’t want to have the center stage. People put React in the center stage.
In my experience, this hasn’t been the case. React outputs some HTML, and you still have to know what HTML to use and how to use it.
Nice 👍🏻
to be fair I’m using it for one hobby project (not a webdev at day) and still feel overwhelmed by the amount of additional plugins (Redux, how do you do global state for login + user-menu that is decoupled, hooks…). Add the whole JS stack on top (with its 40k modules) and you are starting to feel like every major rust dependency graph is irrelevant in comparison. Vue felt way easier but lacked one major component for one thing I needed (QR code display) and often it’s not obvious how you use any external JS libraries together with these self-managing frameworks.
Don’t do Redux. Only big sites warrant using it. Start with minimal hooks (useState) passing the state and setters as params. everywhere. Once you understand this, work your way up using contexts and effects. You don’t need more than this for 80% complex sites out there.
thanks for the explanation, will look into that
contexts and hooks are probably the best thing to happen to react since JSX
UML works for a very narrow view of software development. I found that out when I wrote my thesis in OCaml and had to document it in UML. How do you express a curried function in UML? How do you express a simple function in UML that happens to not be attached to an object?
And that is only the tip of the iceberg.
How do you express a complex SQL query in UML? Entity-relation diagrams help modelling the schema, but what about a query with joins and window functions?
How do you express a map-reduce operation in UML? What’s the diagram for filtering a list, then aggregating the results?
How do you express a minimally-complex algorithm in UML (think Sieve of Erastothenes)? Why can’t I use pseudo-code, which beats diagrams every day of the week?
How do you express a reactive user interface in UML? Why do I have to use something like a collaboration diagram, when the state of the interface derives directly the representation of the interface? It’s not the case that the input enables the submit button when it finds itself no longer empty.
How do you express a Prolog program in UML? Do you think the UML committee has ever known about the existence of Prolog?
How do I represent a multi-param generic data structure? For example, a mapping/hash table from A to B, where A and B are arbitrary types.
And then we ignore the old problem of documentation and software diverging over time, and having to update the documentation when the software changes.
UML comes from an era where Object Oriented (Obsessed?) Design was the whole world, and everything that wasn’t OOD was being forced to not exist. In this era, Architects would draw UML diagrams and cheap Indian code monkeys would do the coding, because we thought that code monkeys would be cheap and replaceable. This was perfect for the MBAs because programmers started to be annoying by not delivering on the expected deadlines, and claiming about the algorithms, the damned algorithms. We wanted to replace them with cheaper and less-whiny ones.
Turns out that the details of the system are in the code, not in the diagrams, and only the trivial parts of the system would be expressed in diagrams, and the devil is in the details. But this makes programmers non replaceable and fungible, because the better ones can express algorithms that the worse programmers will never understand. And many times, you need these better algorithms.
All this makes UML not anymore the silver bullet for bodyshops. The broken idea of hiring 1 architect and 100 code monkeys from the consultancy just doesn’t work, because the architect is not expected to dig in the details, and the 100 code monkeys will just make a mess of the details.
UML was dead on arrival when it ignored most of the details of Software Development. Doesn’t mean though that some parts of UML can be salvaged, such as sequence diagrams, state diagrams, or entity-relation diagrams. But trying to model in UML an arbitrary problem and solution is likely to become wrestling with the language to express anything minimally complex or different.
The UML isn’t supposed to model software down to the query and the algorithm. It’s supposed to model a system and its processes, to assist with the design of a software implementation of that system.
If the way you think about modelling a system is “what complex SQL queries will I need, and how can I add a functional reactive UI” then indeed the UML will not help as the paradigm of its creators and users is the object oriented paradigm. You are thinking about the problem in a way the UML will not help you to express, so we do not expect it to help with the expression of those thoughts.
The OO paradigm of the UML isn’t the “objects are inheritance and encapsulation” straw man of blog posts about switching to functional, but the “objects are things in the problem domain reflected in software” of object oriented analysis and design.
To the extent that the UML and other tools from OOSE were “dead in the water” (an odd claim given their former prevalence), a more convincing reason is that they were sold as efficiency and productivity bolt-ons to programmers without also explaining the need for a paradigm shift. A large number of companies adopted Java, carried on writing structured software, and noticed that they were also having to do this O-O ceremony that doesn’t help them.
In a few years time they’ll notice that switching to Scala, carrying on writing structured software, and also doing this functional ceremony isn’t helping them, and we’ll all get to have this thread again on the “has currying died and nobody noticed?” posts.
Then I would like to have the class diagram removed from UML, please, because class diagrams define a lot of details of data structures, and that restricts a lot on the algorithms I’m allowed to use.
And now you are agreeing with me that many problems and solutions cannot be expressed in UML unless I twist the problem/solution to fit UML.
This is not about Object Oriented Programming vs Functional Programming. This is about the fact that I can’t express many things in UML, starting with the whole universe of functional programming, and continuing with database interactions, declarative programming, advanced data structures, compositional semantics, and many others that I haven’t got time yet to study. Each of those alternative ways to do computing beat the others in specific situations, and having to use UML just forces me to not be able to use the right tool for the job because the UML committee decided that it is hammers for everyone. And now I have to make this screwdriver look like a hammer to be able to draw it in UML.
Firstly, you’re welcome to not use class diagrams. Secondly, you’re welcome to only put the details you need into a diagram, and avoid constraining your implementation: the map is not the terrain.
I don’t think so. It sounds like you’re saying the UML is bad because you can’t do these things, whereas I’m saying the UML is good when I don’t do these things. “Doctor, it hurts when I lift my arm like this!”
I hate when people use this analogy. If it hurts when I lift my arm like this, that’s probably a sign of some deeper underlying problem! Don’t tell me to not lift my arm.
That straw man is exactly what I’ve been taught at school. Shapes and animals and all that. Sure it’s just for learning, but the next step is invariably “OO is hard”, “OO is complicated”, “OO takes times to master”… the usual conversation stoppers.
That also is likely a mistake. As Mike Acton so eloquently put it, we should not code around a model of the world (in this case, the things in the problem domain). We should code around a model of the data. The things in the problem domain are what you speak of before managers or domain specialists. When you start actually programming however, it quickly becomes about the shape and size and flow of the data, and UML doesn’t help much there.
What does that even mean ? I have never seen this aspect explained except in the most superficial terms - creating a class which has a method name that reflects something from the domain usually resulting in bikeshed arguments.
I am sorry but that is not a model of anything. It is just naming. We can’t call naming modelling. When I am making a model of a circuit in software I can interact with the model, test all the assumptions. When an actual architect uses AutoDesk they are modelling things and can interact with them. BDD does this so it can absolutely be called modelling in some sense. I don’t know if it is the best modelling technique we can come up with but it works.
I’d recommend Eric Evans’s book on domain-driven design. “Just naming” implies that you’ve already decided what the method does and that you want to find something in the problem domain to map it onto. OOA/D says that you have found something important in the problem domain and that you want your software to simulate it.
Spot on. Frankly speaking, this was already crystal clear for many people back them. Many people realised immediately it was snake oil.
I also posted about implementing a similar component. I didn’t cover handling the error state (so I appreciate that you did), but one problem I’ve run into was that when switching between two subtrees with the data loader component at the same level, the component wouldn’t actually be unmounted, which broke a few of my assumptions. I think wrapping it in another component like you did in the final
CommentsSection
implementation fixes this, though.Also somebody in the comments also pointed to the Apollo Query component, which has yet another API but essentially does the same thing.
Old components may not be unmounted because the final tree contains the same component in between state changes. This can be the case if all your sub-components are divs, for example. In that case, React will happily not rebuild the component, and instead will just change the props of it. Then you need to use the componentWillReceiveProps, that only complicates it and makes for more antipatterns and worse code.
Depending on how you can refactor it, you can trick React into rebuilding the whole component, for example by returning an array of a single component with a random key, or some kind of key that changes when you want the DOM tree to be rebuilt. It is a bit annoying, but it’s significantly less annoying than the componentWillReceiveProps route.
Yep, componentWillReceiveProps has only caused me problems. That’s why I prefer hooks in general, they make it easier to spot this kind of errors and correct them “the right way”.
I must be confused. You’re saying that every application should enumerate every potential combination of data, one-by-one? I don’t see where algebraic datatypes are being applied.
Hi!
Enumerating every potential combination is obviously impractical. But thinking about it helps you design types in a better way so that you can reduce the amount of data that is valid. One of the principles I use when designing datatypes is looking for groups of data that are absolutely independent of each other, and thinking about them separately. Then, for the groups of data that are somewhat dependent on each other, there is usually one field that determines the others in some fashion, and I split the data based on this field.
The example applies the underlying ideas behind ADTs to look for the final solution. In Haskell, you could think of something equivalent in the style of:
I think I understand. But this seems like an application of structural typing to enumerate permissible combinations of fields, rather than of algebraic datatypes.
It seems to me that the way these are worded is rather confusing. I can figure it out eventually going through each line, and the #2 creates a contradiction with #3. It seems to me that if #3 is true, then #2 cannot be true.
It definitely could be more clearly worded. I think it’s saying:
Thanks zzing and chenghiz for the kind suggestion! I’ll tune down the poetical approach to writing.
I hope they add something like pattern matching to make checking through the cases of the ADT a bit less cumbersome. Given how much ceremony it takes to define and use an ADT, I sometimes wonder if I’m even doing the right thing.
The switch statement is kind of a weak pattern matching. It doesn’t allow you to match on the fields or extract field values easily. I still consider it a good tool for your toolbox, specially if you do Redux. In Redux, you have ADTs, either explicitly declared, or implicitly declared. I prefer explicit in terms of types, so at least the typechecker can spot some of my mistakes.
What I never understand with smart contracts that require off chain data:
If it requires an oracle, why not just run it off chain? In this case you’re fully trusting the exchange, so it’s not any more “trustless” than if the buyer’s wallet had an integration with ShapeShift/Changelly/some other instant exchange.
What if you want to deal with the average exchange ratio? How do you convince these instant exchanges to cooperate to provide the average ratio, considering that they may not want to cooperate? On the other hand, asking each one for an oracle, and having a smart contract that accepts several oracles to compute the average is not that hard.
Why? The average isn’t useful (in the context of the article). If I want someone to send me $10 USD, I’d ask the exchange how much $10 USD in Bitcoin is, and ask for that amount. The average would mean that I could get more or less depending on which exchange I have an account with.
I think the idea is that you trust external parties to provide data (because it can be audited), but you don’t trust external parties to run code. I think it makes sense.
This is pretty much the takeaway. You can run trusted code on chain, while using trusted data from off chain.
You can also run the code yourself.
i.e. User types in their wallet app to request $10 USD. The wallet secretly makes a deal with the user’s exchange and determines that $10 USD will be worth X Coins. The wallet makes a request to the other person for X coins. Using oracles just adds extra cost for not a lot more trust.