so I’ve decided that I want to dip my toe in functional programming by doing some DailyProgrammer puzzles in Python (the language I’ve used for like 15+ years) but I’m going to do it in a declarative style and treat all data as immutable. I also found a cool functional library called toolz that I want to play around with
toolz definitely has the Clojure “problem” (it’s a problem for beginners like me, anyway) of being cluttered with a fuckton of different functions that are slight variations on things like reduce() or map() or etc., but I wonder if being able to concisely express something like diff(), for example, might be genuinely useful. instead of having to express this as a filter() with a lambda (which is much more explicit)
the reason why I’m not going with F# is just because I feel like the strange syntax (= for comparison, <> for not equal, <- to change a variable, and whatever the fuck | :? is, for starters) would add a lot of cognitive load that would be pretty demoralizing on top of me trying to learn a whole new programming paradigm and feeling dumb and out of my element. so I want to stay at least partly in my element while messing around with this, which is why I’m sticking with Python
although, I might try F# in the future! I definitely don’t like Lisps but it seems like other functional languages have something called monads which are basically like the functional programming version of classes. they address one of my biggest problems with Lisp-like languages, which is that there’s seemingly no way to add context to your data (like “this dict represents a catgirl”) or to group functions that operate on a certain type of data (like “this is a catgirl method that returns a matching botgirl”) or even to clearly indicate what kind of data your functions expect to get
here’s my first attempt! https://pastebin.com/V9EPpMTz
what do y’all think? I have no idea what I’m doing and I feel like my code might be kinda hard to read, but I don’t know how to make it any more readable, either. also I just find functional-style code hard to read in general so I’m not sure if my intuition is worth anything here
@kasdeya nice! looks pretty reasonable to me.
here's a Haskell solution I put together if you'd like to compare: https://play.haskell.org/saved/ZwNQqL7j
(the playground times out before it gets anywhere near finishing summing over the first 10,000 practical numbers, so i don't really know how efficient my solution is)
i think the main difference between our solutions is how we split functions up. i like to have them be slightly larger, so that each function can be reasonably motivated in isolation. you have more, smaller functions than i would normally have ^_^;
@liese hehe - yeah I definitely felt like I was making too many small functions. I was hoping that by having a lot of little functions, it would be easier for me to understand the bigger picture of what was happening, but I still think that my code is pretty hard to conceptualize - especially all_combos() which I’m not even sure if I could articulate what it does without giving examples or using a metaphor or something
I’m glad that it isn’t just like terrible code or anything though lol. also wow Haskell is so much more concise! I’ve tried to learn Haskell in the past so I can understand a lot of this code, but I’m definitely lost trying to understand isPractical and main lol
@kasdeya it can be very concise! the dot operator is (right to left) composition, so you can write (f . g) x instead of (f (g x)), and the dollar operator is application with different precedence, so you can write f . g $ x instead of (f . g) x. idiomatic haskell uses a lot of infix operators ^_^;
@liese ohh interesting okay! so for this expression:
map sum . combinations . divisors $ n
could it be written as this?: (I’m using Python syntax here because I’m more familiar with it)
map(
lambda i: sum(combinations(divisors(i))),
n
)
I think that Haskell’s order of operations might turn the line I took from your code into this:
map(sum . combinations . divisors, n)
but I’m not totally sure if that’s how it works, and I don’t really know what would happen to the currying at that point
also for languages with curried functions, how do you keep track of how many arguments have been used up and how many are left to go? do LSPs give you that information so you can make sure that you’ve used up the right number of arguments?
and does it ever get confusing that function application happens in reverse order? like the original data is on the right, then each subsequent function is to the left, so I feel like you might need to read it right-to-left in order to understand what’s happening
also is there a cleaner way to chain three function applications like this?:
takesThreeArgs 0 x 1 = -- something I want happens here
myChainedFunc x = takesThreeArgs 0 (takesThreeArgs 0 (takesThreeArgs 0 x 1) 1) 1)
because if x was the last argument then you could just use currying to fill in the 0 and 1 but it’s the second-to-last argument instead
sorry for all of the questions but I’ve always wondered about these things
@kasdeya no worries!!
for the first question, not quite -- `map sum . combinations . divisors $ n` desugars to a Python-style `map(sum, combinations(divisors(n)))`: we start with `n`, then apply the `divisors` function to it to get its list of divisors; then apply `hte combinations` function to *that* to get a list of all possible combinations of divisors. finally, we map `sum` over this list, turning each combination into a single number. in the end we have a list of numbers, each obtained from one summed combination.
in F# (i think?) we could write this in a more natural left-to-right style, and with just one left-associative operator: `n |> divisors |> combinations |> map sum`. i prefer this, but it's not idiomatic in Haskell...
in general in Haskell, if you have `f . g . h $ x`, that turns into `f(g(h(x)))` -- with *x* being passed to the *innermost*, i.e. *rightmost* function.
> also for languages with curried functions, how do you keep track of how many arguments have been used up and how many are left to go? do LSPs give you that information so you can make sure that you’ve used up the right number of arguments?
i don't usually think about this 🤔 whether i'm writing `foo(x, y)` or `foo x y`, i still need to know or discover the number of arguments somehow. knowing that the function is curried means that I can *also* write `foo x` instead of writing a longer `lambda y: foo x y`. i don't think in the other direction -- currying essentially always lets me *omit* arguments i already know it (eventually) takes
but yes, LSPs and IDE integration will often tell you what arguments the function will take, same as in any language. sometimes it's even pretty easy to ask what the type of a given subexpression is; that comes in handy pretty often in Agda (the language i tend to use for my research)
> and does it ever get confusing that function application happens in reverse order? like the original data is on the right, then each subsequent function is to the left, so I feel like you might need to read it right-to-left in order to understand what’s happening
you kind of get used to it, just like how `f(g(h(x)))` also has to be read right-to-left ("do h, then do g, then do f"). most languages, imperative and functional, suffer from this :( but yes, i *love* when i can use a left-to-right pipelining operator like the `|>` example from earlier!
> also is there a cleaner way to chain three function applications like this?:
blargh. not directly in haskell, although Scala and C++ (?!?) have some clever ways to do partial application in a non-left-to-right order (something like `takesThreeArgs 0 _1 1` in C++, iirc?)
the way i'd do it here is:
```
myChainedFunc x =
let f y = takesThreeArgs 0 y 1 in
(f . f . f) x
```
it's not particularly succinct, but at least it is clearer what exactly is getting chained. if the function only has two parameters, sometimes you can get away with using `flip`, which swaps the first and second parameter. in my experience though, `flip` just makes things more confusing, because you're not used to seeing your function take arguments in the opposite order.
@kasdeya just looked up how the C++ stdlib provides partial application -- it's incredibly clever and makes you wonder how they made it work *as a library* (not even a language feature). the mind boggles.
https://en.cppreference.com/w/cpp/utility/functional/bind.html