Learn Physics with Functional Programming
nostarch.com
nostarch.com
I'd really like to see a "spiritual successor" to Structure and Interpretation of Classical Mechanics--something that can take off and achieve a life of its own.
SICM is open-source, and many people have implemented their own versions of parts of it, but I would love to see a vibrant and active community develop around such a beautiful computer algebra / computer-physics system.
SICM goes far beyond simple Newtonian mechanics, implementing calculus, Lagrangian and Hamiltonian mechanics, and differential geometry, and probably a whole lot more that you just have to spelunk into the source code to discover.
(Here's a book about the differential geometry implementation in scmutils: https://mitpress.mit.edu/9780262019347/functional-differenti... as seen in HN: https://news.ycombinator.com/item?id=7884551 )
[1] https://docs.sympy.org/latest/tutorials/intro-tutorial/print...
My impression is that it can be a very frustrating way to learn mechanics if you don't have much interest in functional programming.
I cut my teeth on SICP before going into physics, so I was perhaps the exact target audience.
I also see Scheme as an improvement over its successor languages.
The authors explain: "Classical mechanics is deceptively simple. It is surprisingly easy to get the right answer with fallacious reasoning or without real understanding. Traditional mathematical notation contributes to this problem. Symbols have ambiguous meanings that depend on context, and often even change within a given context."
Read the rest of the preface here: https://mitp-content-server.mit.edu/books/content/sectbyfn/b...
And why not just "code" but "functional code"? Well, it makes a lot more sense to "take a derivative of a function" if that function doesn't have side effects (etc). There is a tighter correspondence between functions in the programming sense and in the mathematical sense.
Maybe someone else can shed light on the MIT mindset. Certainly some of Walck's points apply to Scheme as much as to Haskell, but Scheme lacks the type system, syntax and syntactical "convenience" of curried functions. The basic strength of functional programming is the lack of complex imperative book-keeping: your code looks more like math.
My impression is that SICP and SICM are eccentric.
The argument is that all of that syntax is a distraction.
Although that's not a terrible idea, I have never actually seen any major scientific code that was based on functional programming and was significantly faster than its non-FP competitors. My guess is that the folks writing the codes are already pretty smart, not doing any extra work that could be easily removed, and already take advantage of algorithms that use non-functional paradigms which give them significant speedups
The thing about performance in scientific programming, it is often binary: You either need the very best, or you don't care about it at all. Unlike other areas of programming, there is no middle ground. If you need your scientific code to be performant, then you need to squeeze every last bit of performance out of your hardware, which you can only do with something like Fortran or C. If you don't care about performance, then it doesn't matter. That's why Python is so popular.
Ideally I would love for something like F# to replace python in the scientific computing space, but the ecosystem is so much larger in python. That's what matters to most scientists.
The analogy I think of is is tree traversal. A smart person can write an optimal tree traversal algorithm and make their program finish quickly, whether or not the user requested that part of the algorithm's results, but FP can realize the program doesn't output the tree, so traversing it can be skipped. OK, that's not a great analogy but the point is that in principle, FP optimization could find a cheaper way to produce the same exact values as a simulation written in a non-functional language.
In finance, which has a lot of parallels with scientific computing but tends to end up with semi-secret, parallel, competing implementations of the same ideas, functional programming has had significant (though by no means universal) success in doing exactly what you describe.
All the major players in these fields read each other's code and papers and steal ideas
In other areas there are no competitors, there's just "write the minimal code to get your idea that contributes 0.01% more to scientific knowledge, publish, and then declare code bankruptcy". And a long tail of low to high quality stuff that lasts forever and turns out to be load-bearing but also completely inscrutable and unmodifiable.
After typing that out I realize I just recapitulated what you said in your first paragraph. My knowledge of finance is limited beyond knowing "jane street capital has been talking about FP for ages" and most of the people I've talked to say their work in finance (HPC mostly) is C++ or hardware-based.
Thanks to https://2.maria.cloud, everything in SICM and FDG works in the browser as well: https://2.maria.cloud/gist/d3c76ee5e9eaf6b3367949f43873e8b2
There's still not a great map of the project (from primitives to general relativity), but many of the namespaces are written as literate programming explorations: https://emmy.mentat.org/#explore-the-project
Here's the automatic differentiation implementation/essay, for example: https://sritchie.github.io/emmy/src/emmy/differential.html
A rough sketch of the tower is:
- `emmy.value` and `emmy.generic` implement the extensible generic operations
- `emmy.ratio`, `emmy.complex` and `emmy.numbers` fleshes out the numeric tower
- `emmy.expression` and `emmy.abstract.number` add support for symbolic literals
Next we need an algebraic simplifier...
- `emmy.pattern.{match,rule,syntax} give us a pattern matching language
- `emmy.simplify.rules` adds a ton of simplification rules, out of which
- `emmy.simplify` builds a simplification engine
Actually the simplifier has three parts... the first two start in `emmy.rational-function` and `emmy.polynomial` and involve converting an expression into either a polynomial or a rational function and then back out, putting them into "canonical form" in the process. That will send you down the rabbit hole of polynomial GCD etc...
And on and on! I'm happy to facilitate any code reading journey you go on or chat about Emmy or the original scmutils, feel free to write at sam [at] mentat.org, or else visit the Discord I run for the project at https://discord.gg/hsRBqGEeQ4.
But I am also quite interested in learning more about the "under the hood" workings and software craftsmanship of scmutils. The textbook _uses_ scmutils to explore classical mechanics.
But it does not delve into the implementation details of scmutils itself, which interest me.
I did find this.
"Working with APL for Physics Research - Kostas Blekos - Dyalog '17" https://www.youtube.com/watch?v=pWtvRlCdX00
Would be interested in reading a more detailed account.
Sadly for simple realization for example the twin paradox without acceleration (3 astronauts handing over clock info instead of using acelearation). It is not there. And doing graphic … and simple wedge. Sadly.
May be someone here can highlight some sites for this and that.
For chapter 14 I wonder whether a Jupiter notebook (which can do Tex if using Matplotlib … have not tested it as I used texshop and screen capture from Matplotlib instead).
Obviously total functional programming is a problem.
But the GP greatly simplifies electrodynamics and removing the need to track handedness in your basis is very helpful in my experience.
Beyond the practical value you'll also discover the delightful beauty with which you can express things in Haskell, and these sparks of joy could give you a new motivation to grow and appreciate deeper, beautiful ways of coding and thinking.
At the least, Haskell is much faster than Python, the language itself is not that complicated (learning a whole new ecosystem is a different story), FP might lend itself better to translating physics into code, and some elements of it carry over to other languages, for example, type classes and Rust’s traits.
I find Haskell quite complicated, involving abstract concepts you would likely not consciously encounter in other languages (lenses, monad transformers, free monads, etc).
You can do so much with just data declarations and typeclasses.
https://www.simplehaskell.org/
(disclaimer: not a huge fan of the sight design, but I fully support the concept)
Add react to the list
Python mostly hits an optimal point for my needs though and I didn't have to read 50 articles trying to explain what a Monad was. Scripting languages just click a lot better for me (less ceremony). There are some weird languages I like such as APL. APL is also very mathy, but there's essentially no ceremony (just some funny symbols that are easy enough to learn).
The thing about "Monad" is that it's an absolutely trivial interface. You'll know you actually understand it properly when you go "oh, that's all." Is it hard to describe? Not when you understand the underlying concepts. But if you demand to skip them, you're only going to confuse yourself.
Your mileage will vary of course. This was just my experience. I know Haskell is powerful, but it wasn't right for me. I haven't met a scripting language I didn't like yet though, so perhaps my neurons are just wired for that now. I do enjoy the random APL or Forth problem though and I'm decent at SQL after using it often for a decade.
IMHO that book is bloated and overrated. Maybe it works for some people, but I find the explanations really weird and some of the exercises are just useless busywork without a point.
There are good parts in there (they do try to teach you how to properly think about types which is nice) but I really feel like you don't need to read 1000 pages to learn basic Haskell.
main = do
x <- readLn
y <- readLn
print (x + y)
Do I have to 'understand "Monad"' to write this? def main():
x = input()
y = input()
print(x + y)Literally every line?
Your example makes it seem so simple! "Do" for function declarations, "<-" for variable assignment... Haskell is just like python!
Alright, here I go!
fact n = do
n' <- n - 1
if n <= 1 then 1 else n * fact n'
Uh oh... Couldn't match type `t' with `m t'
Expected: t -> m t
Actual: m t -> m t
What the heck is "m t"?It's worse than that! What the heck is "->"?
But I don't particularly buy the argument. Alright, here I go:
def main():
x = input
y = input
print(x + y)
Uh oh... Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'builtin_function_or_method' and 'builtin_function_or_method'
What the heck is "Traceback", "<stdin>", "<module>", "TypeError", "operand", "builtin_function_or_method"? Did I have to understand all those to write the simple Python program?The claim that you have to understand everything about a failure case in order to write simple programs doesn't seem valid to me. It doesn't resonate with how I've learned languages before. However, I may not have the best perspective because I'm very familiar with both Python and Haskell so I don't have the "beginners mind" any more. I would be interested to hear from someone who's a beginner (perhaps in both languages) whether they think the Haskell program requires more understanding than the Python one https://news.ycombinator.com/item?id=37402253
This isn't the case in Python where input and output are standard functions. The syntax for function declaration and variable assignment just work.
If our requirements changed to now read and sum an arbitrary number of inputs together, I'd expect a beginner could change the Python program to do so. I'm skeptical they could make the required changes to the Haskell version (without understanding monads).
Right, and the Python errors mention all sorts of things a beginner is not expected to understand, like "builtin_function_or_method". I don't think "understanding builtin_function_or_method" is required to write the Python and I don't think "understanding Monad" is required to write the Haskell.
> If our requirements changed to now read and sum an arbitrary number of inputs together, I'd expect a beginner could change the Python program to do so. I'm skeptical they could make the required changes to the Haskell version (without understanding monads).
Where do I have to "understand monads" to write this?
main = do
iters <- readLn
let loop n total =
if n == iters
then print total
else do
x <- readLn
loop (n + 1) (total + x)
loop 0 0
I had to understand that in Haskell looping is done via recursion (which is mindbending initially, if you come from imperative programming), but I don't see that I had to understand monads. FWIW the Python that I'd write is def main():
iters = input()
total = 0
for _ in range(iters):
x = input()
total += x
print total
This is simpler. But I could write a Haskell version that uses an IORef to get roughly the same code structure as the Python. main2 = do
iters <- readLn
total <- newIORef 0
for_ [1..iters] $ \_ -> do
x <- readLn
modifyIORef' total (+ x)
theTotal <- readIORef total
print theTotal
I would say this requires understanding the mutability/immutability distinction to know why we use an IORef at all (but you'd get that with OCaml[1] too) and it still requires understanding do notation, but I don't see that it requires "understanding monads"!Now, if I were writing this for real I probably would use a version with a state monad transformer so it was
main3 = do
iters <- readLn
total <-
flip runStateT 0 $ for_ [1..iters] $ \_ -> do
x <- lift readLn
modify' (+ x)
print total
That would require some understanding of monads so that you can understand how they are transformed with monad transformers![1] https://www.cs.cornell.edu/courses/cs3110/2018sp/l/14-mutabl...
>>> def factorial(n):
... 1 if n <= 1 else n * factorial(n - 1)
...
>>> factorial(1)
>>>
Why is nothing happening?100% yes! Took me quite a while to figure out what was wrong with your example. This is by far my biggest frustration with Python.
Perfect example, by the way. You've exactly captured the sort-of "semantic mistake" I was trying to convey: technically valid syntax but the meaning doesn't match the intent.
Fixing your example requires understanding how functions return values, a concept pervasive throughout Python. The first function you ever wrote probably returned something.
Fixing mine requires understanding how `do` and `<-` interact, which I remain skeptical can be done without exposing oneself to monads. My example should not use `do` at all, I'm actually surprised it can be made to compile with it.
I think this is the major point of contention between us.
I don't think that understanding how to use `do` requires "understanding monads". My example was to demonstrate that understanding how to use `do` for the IO monad is not really much different from understanding how to write a sequence of statements in Python. There's one big difference: you have to use both `<-` and `let`, and you have to know that the former is used to get a value out of something of type `IO a` and the latter is just normal variable binding. But that still doesn't require "understanding monads"! It just requires knowing there's a special thing called `IO` that wraps things with effects.
I do think that understanding how to use `do` for arbitrary monads requires understanding monads! But that was not required to solve the problem lmm originally proposed, nor your extension.
I also do think that most pedagogical Haskell material focuses too much on "understanding fine details" and that applies particularly to monads. My point is that another approach is possible. That is simply to use `do` notation intuitively via analogy with imperative languages (which is a perfectly fine thing to do).
fact n = do
let n' = n - 1
if n <= 1 then 1 else n * fact n'
Here you go. Not sure about Haskell but in PureScript it compiles. Use "<-" for functions which return a value in IO type constructor, otherwise use "let". fact n = do
let n' = do n - 1
if do n <= 1 then do 1 else do n * fact n'The recommended explanation of what <- is/does will be about "Monad".
Yes, you could just treat IO as a black box and memorize a bunch of weird syntax rules, but you're going to have a hard time very quickly, especially when you change something and the compiler throws errors at you.
FWIW, I think that monads are a powerful abstraction, but they're certainly not incredibly natural and Haskell basically just throws them at you from day 1.
Sure, some. I think this discussion is about exactly what and how much, when it comes to Haskell.
> the recommended way of doing that is to understand Monad.
That doesn't ring true to me, not from how I learned Haskell and not by analogy with how I learned other languages. I don't remember having to "understand Monad" to write in "do" notation. Quite the opposite in fact. I vividly remember trying to "understand Monad" and being unable to, and then just giving up and trying to use do notation, finding it easy, completely unhelped by what I was trying to "understand", and regretting having spent all the time "understanding". But it's been a long time since I learned a language so maybe I'm mistaken. I would appreciate the point of view of others who have fresher eyes.
I will concede that people have a harder time learning Haskell than Python (although I don't think anyone understands the exact reasons) but you gave a very specific example of a program that is supposedly requires some deep understanding to write in Haskell. I wanted to give a counterpoint so people can decide for themselves. That's as far as my claim goes.
OTOH, the promise of Haskell is that while it takes longer to learn and getting used to, the payoff is that you can get rid of certain classes of bugs. Whether that's true or not is a different matter entirely, but a priori just because something is hard to learn it doesn't mean it's not worth it.
Why?
I've never worked this through to a full conclusion, but you could even write it in a way that would let you get symbolic differentiation out of it too.
See https://sritchie.github.io/emmy/src/emmy/differential.html for detail!