420 karma · joined February 20, 2009
Over the last 3 or 4 decades, our procedural and OO languages have slowly been absorbing the best ideas from "FP languages." We're getting to the stage where the very idea of a "functional language" is eroding.
If you're looking for programming languages with no support for imperative programming, Excel is pretty much it. Even Haskell has robust support for sequencing actions. If it didn't, I don't think we'd be talking about it at all.
What I predict is that ideas from FP will continue to bubble up into the mainstream. Like prior inventions, they won't be presented as a thing that asks you to rework all of your algorithmic code. They will instead be polished and presented in a way that makes them work more as extensions to what you can already do.
If you squint a little bit, Haskell's do-notation is already available in mainstream languages in the form of async/await syntax. async/await is not quite as general as the original Haskell solution, but it also doesn't ask you to completely rethink the way you design algorithms.
Lambdas are now a requirement for any modern programming language. Monadic promises are how asynchronous programming is done. Rust is basically OCaml except with typeclasses and without a GC.
Inch by inch, ideas from FP bubble up into mainstream programming.
I predict that algebraic effects (a la Koka) are next, but it'll be a while before the industry is ready for it.
The REPL that we offer in the distribution doesn't include any of the analysis logic and it's just 1.7mb once compiled (on my M1 Macbook). I'm not sure how much smaller it gets if you omit CodeGen.
Luau can be pretty small if you need it to be.
I was going to try to pick out one of my favourites from this series, but I really can't. Every last one is a treasure.
EDIT: Oops! This is based on Aphyr's work. My bad!
We'd like to afford the same kind of ideas for native Luau code, but we're not there yet.
In particular, I am pretty sure that it won't do the right thing when it encounters the extra syntax we've added to Luau. So far, that's an if-else expression and a continue keyword. Transpiling those accurately will take a bit of work.
It's powerful enough that you can write your whole game in C++, but not so powerful that you should rely on the system allocator for anything.
It's in a lovely sweet spot for a particular dialect of zero-overhead C++ that's really fun to write.
As a simple example, I had a little class library of data types that were crafted to exactly map to the hardware registers of the GBA. The whole thing inlined away to nothing.
Some companies earn the privilege of a super tenured core team of engineers who work on their product for an extended period of time. They will choose different tradeoffs from a team that needs to adapt to higher turnover.
I have had pretty good luck cheating this in a bunch of ways: I use a small font, a big display, and I use a terse programming style.
Once you internalize a bunch of common higher-order functions, you learn how to draw a ton of meaning out of a relatively small number of terms.
I've never owned a keyboard that was actually physically labelled for Dvorak, so I _cannot_ type if I can see my fingers. The lies printed on the keycaps throw me off every time. :)
It's not really a big deal because things are also always introduced at the latest possible position. Each is also typically given an initializer. I'd consider it suspicious if I were to see C++ that declared 3 uninitialized pointers back to back like this.
And then you get to the codebases where the authors have chosen to embrace auto and type inference... :)
I actually hadn't heard of Teal! I don't think it existed when we started.
Judging from the Teal documentation, I'd venture that it looks pretty similar to Luau in everyday use.
If I were to guess, I'd venture that the biggest differences are probably the kind of type inference we do (Luau's inference engine draws inspiration in equal parts from OCaml and TypeScript), a bunch of Roblox-specific features, and that our inference engine is all C++ for performance.
It's Lu-au. It's like a party in your development environment!
The appeal of games like Dark Souls (and Slay the Spire!) at high difficulties arises from the knowledge that you _will_ win if you are clever and observant enough.
It's the learning process that keeps players coming back to these games, not the Game Over screen.
We had a fantastic unit testing harness[2]. With effect tracking, you can arrange for your harness to put precise fences around nondeterministic code. You don't need to rely on experience and discipline to ensure that tests are reliable or fast. The type system does it for you.
Effect tracking also makes concurrent code easier to write. You have a great deal of control over how and when threads can be created, and what kinds of logic is permitted on those threads.
GHC's IO manager offers green threads and async IO, so rather than a select loop, you just fork threads and perform "blocking" IO calls. You get about the same efficiency as a select loop this way.
Lastly, something we learned to appreciate over the years is that Haskell is easier to refactor. When you combine the rigid type system, effect tracking system, and ease of writing unit tests, it becomes very easy to refactor old Haskell. It doesn't really matter how bad the old code was or whether the original author is around to help you at all.
If you intend for your product to evolve continuously forever, this is truly a secret weapon.
[1] https://andyfriesen.com/2014/03/25/what-its-like-to-use-haskell.html
[2] https://andyfriesen.com/2015/06/17/testable-io-in-haskell.htmlI frequently do light computing on a Surface Go. It's a delightful little device and I love it, but it is not powerful enough that I can leave gmail, Slack, and Discord open all the time.
I don't have enough RAM to run another web application but I could very easily afford a native app or two.
Neither OO nor pure FP are required to build high-quality software!
OCaml developers have been living in this world for decades now and they seem to get by just fine. Further, they do this purely by choice! Their language has had OO syntax for decades and they are happy ignoring it!
In order to bridge the gap, you have to make some pretty terrible assumptions:
If your code is not OO, it must be FP.
If your code is FP, it must be pure. Therefore,
If your code cannot be pure, it must be OO.
The OCaml community presents a pretty compelling third option:
OCaml is billed as a 'functional language,' but it doesn't do anything to prevent you from performing mutations or executing side effects anywhere you want. It even has builtin syntax for "for" and "while" loops.
Interestingly, OCaml does afford classes and objects but hardly anyone seems to use them. It's not that OCaml objects are weird or difficult or bad in some way. People just choose to write records and functions. Some of those records have functions in them. Some of the functions mutate state.
In the OCaml world, at least, pretending OOP never happened seems to have worked out just fine.
It's random numbers, the system clock, data off the internet, and input events from the user.
What it sounds like is that Mozilla has come up with a debugging environment that bottles all that nondeterminism up into a neat little package so that it can replay a specific run of your program any time you want.
It's there primarily so that your development environment can give you accurate feedback when you're using your favourite JS libraries.
If you're able to run the code locally, there is a function called Debug.Trace.trace which writes messages to stdout without requiring IO.
If it's a production issue, all you need to do is figure out what is being passed to your function. It's a pure function, so you can always debug it locally once you've got that.
1) Many of us have also seen the C++ library or application that busts out the template metaprograms at every opportunity. Haskell is the same. Use restraint when bringing in new libraries and programming techniques. (as an aside, I have yet to encounter a single problem that I thought merited the use of arrows)
2) You also see this in other languages, though to a lesser degree. The same techniques we use with other languages work just as well in Haskell: As leadership, form a strong opinion on the best way to write control flow and ensure that everything uses a consistent style.
3) Are your engineers running around implementing random research papers? This has nothing to do with Haskell.
4) In practice, this is almost never a big problem. Refactoring a pure function to an effectful function is easy and safe. In a production setting, almost all of your functions run in some monad already, so it's pretty rare to run into a situation where you suddenly need to percolate effects through many functions.
It's not so difficult if your MacBook trusts Apple, Google, Amazon, and Microsoft out of the box.
It is entirely reasonable that your would-be creditors can refuse to lend you money for certain kinds of purchases.
In our case, we dealt with it by keeping relatively bare, boring code. We avoided point-free style, crazy combinators like lenses, and complex monad transformer stacks except in the 'plumbing' part of the application that didn't need to change very much.
This paid off in spades as we had a lot of engineers who only ever had to work in the 'porcelain' parts of the application. They got a lot of great work done using abstractions that matched their intuition exactly.