3 ms·
I recently saw the title, Learn Physics with Functional Programming, and thought to myself... I know physics and functional programming but I want to learn Hask
by indigosun 2y ago
I recently saw the title, Learn Physics with Functional Programming, and thought to myself... I know physics and functional programming but I want to learn Haskell.
Having gone through it, I highly recommend the book; especially to anyone knowledgeable about any 2 and interested in the third.
I love Haskell. I love writing it, and reading it. Haskell is a beautiful programming language. One where I felt immediately at home in and comforted by.
Currying is one of the foundational concepts of the language, but I do think it is one aspect of the language the community treats excessively sacredly.
In Haskell, function composition has right associativity. What this means, in the event of a function that takes in 3 Integers as arguments and returns a Double would have a type definition of:
FN_NAME :: Int -> Int -> Int -> Double
The right associativity then also means this is equivalent to these type definition:
fn0 :: Int -> Int -> Int -> Double
fn0 x y z = (fromIntegral (x + y)) / (fromIntegral z)
fn1 :: Int -> (Int -> Int -> Double)
fn1 x y z = (fromIntegral (x + y)) / (fromIntegral z)
fn2 :: Int -> (Int -> (Int -> Double))
fn2 x y z = (fromIntegral (x + y)) / (fromIntegral z)
ghci> fn0 1 2 3 -- 1.0
ghci> fn1 1 2 3 -- 1.0
ghci> fn2 1 2 3 --1.0
This is currying in action. A function that takes three arguments is the same as a function that takes one argument and passes it to function that takes two arguments.
As I understand it, the compiler sees ALL functions as a composition of functions of one input.
This is pretty cool, and a powerful foundation to build on. Where I take a bit of issue is when the Haskell community allows the rigor of the type definition to carry some of the weight of the expressiveness of the function definition.
Each function definition ('=') pairs with a type definition ('::'), as the ones above, ie. an example from the promoted book:
type R = Double
type VecDerivative = (R -> Vec) -> R -> Vec
vecDerivative :: R -> VecDerivative
vecDerivative dt v t = (((v (t + dt/2)) ^-^ (v (t - dt/2))) ^/ dt)
velFromPos :: R -- dt
-> (R -> Vec) -- position function
-> (R -> Vec) -- velocity function
velFromPos = vecDerivative
The second function definition: "velFromPos = vecDerivative" ; is equal to saying, "velFromPos dt v t = vecDerivative dt v t". Haskell seems to allows this syntactical ~implicit argument passing because of it's reverence for currying.
-- random example of 'vector-valued function'
v1 :: R -> Vec
v1 t = ((2 *^ (t**2 *^ iHat)) ^+^ (3 *^ (t**3 *^ jHat)) ^+^ (t**4 *^ kHat))
ghci> vecDerivative 0.01 v1 1 -- vec 3.999999999999937 9.000074999999885 4.000099999999962
ghci> velFromPos 0.01 v1 1 -- vec 3.999999999999937 9.000074999999885 4.000099999999962
This has some interesting effects, such as displayed above where the use of currying makes it explicitly clear that calculating the velocity is the exact same thing as calculating the derivative of position.
But I always felt this ~implicit syntax is needlessly aggressive to newcomers.
I also pathologically write my Haskell code like a Lisp because the Haskell community's celebration of the complex precedence structure of its order of operations causes practitioners to appear, to my eyes, over confident in their ability to sight read their own work's order of operations. ;P
ghci> sqrt 2 + 1 * 3 + 3 * 2 + 1 / 7 -- 10.557070705230238
ghci> ((sqrt 2) + ((1 * 3) + ((3*2) + (1 / 7)))) -- 10.557070705230238
I will forever think the second is easier to read, but if you look at many Haskell code bases you will see stuff like the first all over the place, and I have been unable to find a reason why the implicit function arguments and lack of parenthesis syntax is considered idiomatic.
In the face of the ability to write the functions explicitly and with expressive parentheses, it seems the answer is "because you can".
https://nostarch.com/learn-physics-functional-programming https://nostarch.com/learn-physics-functional-programming
- RobMurray 2y agoWow what a lot of parentheses! To me the first example is perfectly clear because it follows the same precedence rules I learned for arithmetic at school, along with most programming languages. There's nothing special about Haskell here, in Python I would write: >>> math.sqrt(2) + 1 * 3 + 3 * 2 + 1 / 7 10.557070705230238 The only difference I see is the parentheses used for function application in python.
- indigosun 2y agoYou should see when negative numbers are involved. Due to Haskell's rigor in regard negative numbers: ghci> -1 + 2 1 ghci> 2 + -1 <interactive>:12:1: error: Precedence parsing error cannot mix ‘+’ [infixl 6] and prefix `-' [infixl 6] in the same infix expression ghci> 2 + (-1) 1 ghci> -1 * 3 -3 ghci> 3^-1 <interactive>:3:2: error: • Variable not in scope: (^-) :: t0 -> t1 -> t • Perhaps you meant one of these: ‘^’ (imported from Prelude), ‘-’ (imported from Prelude), ‘^^’ (imported from Prelude) ghci> 3^^-1 <interactive>:4:2: error: • Variable not in scope: (^^-) :: t0 -> t1 -> t • Perhaps you meant ‘^^’ (imported from Prelude) ghci> 3**-1 <interactive>:5:2: error: • Variable not in scope: (**-) :: t0 -> t1 -> t • Perhaps you meant ‘**’ (imported from Prelude) ghci> 3**(-1) 0.3333333333333333 ghci> sqrt 2 + 1 * 3 + 3 * 2 + 1 / 7 10.557070705230238 ghci> sqrt $ 2 + 1 * 3 + 3 * 2 + 1 / 7 3.3380918415851206 ghci> -sqrt 2 + 1 * 3 + 3 * 2 + 1 / 7 7.728643580484048 ghci> -sqrt $ 2 + 1 * 3 + 3 * 2 + 1 / 7 <interactive>:25:1: error: • Non type-variable argument in the constraint: Num (a -> a) (Use FlexibleContexts to permit this) • When checking the inferred type it :: forall {a}. (Floating a, Num (a -> a)) => a ghci> sin -1 <interactive>:7:1: error: • Non type-variable argument in the constraint: Num (a -> a) (Use FlexibleContexts to permit this) • When checking the inferred type it :: forall {a}. (Floating a, Num (a -> a)) => a -> a ghci> (sin -1) <interactive>:10:1: error: • Non type-variable argument in the constraint: Num (a -> a) (Use FlexibleContexts to permit this) • When checking the inferred type it :: forall {a}. (Floating a, Num (a -> a)) => a -> a ghci> sin (-1) -- -0.8414709848078965 Let's see, ghci> (sin (-1)) -- -0.8414709848078965 Ahh, now that's better.