Fork me on GitHub
#beginners
<
2019-12-18
>
farenzer_house07:12:19

i have a general question about ‘pure functions’ — i’m not really sure where to put this just yet or if it belongs here or in offtopic or elsewhere as its kind of a loose functional programming philosophy question (lemme know if i should drop this elsewhere), but i’m curious about a Clojurist perspective:

GREETING = ->(name){ "hello #{name}" }
def greet(name)
  GREETING.call(name)
end
greet("ryan") # => "hello ryan"
would you say in the above Ruby code that ‘greet’ is a pure function or not? i’m so used to encountering just ‘no side effects // referential transparency’ style definitions that this sort of thing i’ve never even thought about as if ‘pure’ or not — it seems some definitions take into account ‘execution context’ and would call for making GREETING a second argument of ‘greet’ in order to make this pure according to : https://alvinalexander.com/scala/fp-book/definition-of-pure-function — or even the kinda terse first answer here: https://stackoverflow.com/questions/22268851/what-is-a-pure-function — this may not be a pure function: Its result can't depend on _reading_ any hidden value outside of the function scope, such as another field in the same class or global variables. i guess this kinda shocked me as i feel within my clojure study this really hasnt come up as a thing as far as i can remember, and further more seems clojurians would constantly have ‘pure functions within each other’ and still call them pure… again lemme know if i should remove this and drop it in another room 🙂 — thanks!

didibus07:12:16

@farenzer_house It isn't pure no

didibus07:12:16

At least in theory, because GREETINGS is mutable

didibus07:12:01

But I think the rubyness of it might be confusing me a little as well.

farenzer_house07:12:15

oh dang good point with the mutability… wow thanks didnt even think about that — edit: same, im not really a ruby person

andy.fingerhut07:12:37

Reading a global thing that is immutable would, in most people's opinions I believe, not mess up the purity of a function. If it could, then Haskell functions could not call other functions or refer to earlier definitions.

didibus07:12:08

It should be that pure functions can make use of other pure functions and still remain pure themself.

andy.fingerhut07:12:23

But yeah, even "read only" access to a mutable object reference seems to violate most people's idea of a pure function.

andy.fingerhut07:12:41

if there is even one part of the program that can mutate that object.

didibus07:12:07

Its a good question though. Because in Clojure, vars are actually mutable. That's why you can redef them and not have to redef all functions that use them.

didibus07:12:08

So I guess in a strict sense, the code is at risk of having the function you use change between two invocations of itself

didibus07:12:46

But practically, that's not something people really do... So its like practically pure, but maybe not conceptually?

andy.fingerhut07:12:11

I don't think it is straightforward to statically analyze a Clojure program and determine if a particular function is pure or not, even if you make some reasonable restrictions like "assume we never do def or alter-var-root! on any Var". I wish it were straightforward.

andy.fingerhut07:12:50

Amendment: I think that sometimes, but if I saw the restrictions one would have to have to make it possible, I could very easily change my mind.

farenzer_house07:12:31

cool — good food for thought — thanks!!! — my initial reaction was ‘of course thats pure’ but a friend of mine claimed it wasnt and i was shocked by those links ^^^^ i think i’m also at something like So its like practically pure, but maybe not conceptually?

farenzer_house07:12:17

i think in general this does point to something i like about clojure which is that kind of a ‘loose’ adherence to functional programming focusing more on getting things done than conceptual things

andy.fingerhut07:12:10

I mean, even in Haskell you can do unsafe immutable things -- it is just that in that language, it is I believe easier to pin down the list of unsafe things that enable mutability. In Clojure the list of things you can do to cause that to happen is longer, but still fairly short.

farenzer_house07:12:21

seems to hinge on the immutability of that lambda in our context ultimately

didibus07:12:33

One definition of pure, which is my favourite, is that a function is pure if you can memoize it without changing program behavior.

andy.fingerhut07:12:59

which has a subtle dependence upon what equality means in the language.

andy.fingerhut08:12:22

My article on referential transparency that I link to in my next message gives an example of that subtlety: Clojure's definition of = between lists and vectors means that you cannot memoize the function conj if you later want to get correct return results from the memoized conj , when called sometimes on vectors, sometimes on lists.

andy.fingerhut08:12:45

Even though I think of conj as a pure function.

didibus17:12:06

Very cool writeup

andy.fingerhut19:12:22

Glad you liked it

andy.fingerhut07:12:31

This article I wrote a few years back has some of what I (think I) understand on the topic. Happy to get comments/corrections/questions about it: https://github.com/jafingerhut/thalia/blob/master/doc/other-topics/referential-transparency.md

andy.fingerhut07:12:20

And if you want to know more than most people would like to know about Clojure's equality operator: https://clojure.org/guides/equality

farenzer_house07:12:59

ill check these out — thanks a ton for the thoughtful responses everyone!!

dromar5607:12:17

What would be the most idiomatic approach?

(first (filter #(-> % :x #{2}) [{:x 1} {:x 2} {:x 3}]))
Or:
(first (filter (comp #{2} :x) [{:x 1} {:x 2} {:x 3}]))
Or something else?

dromar5607:12:40

I find the comp version more readable in this case (usually I have trouble switching to reading fns from right to left)

hindol.adhya13:12:34

I have the exact opposite problem. Threading macros are literally just arrows. For comp, I can never quite remember the order of execution.

andy.fingerhut08:12:45

I think you will find different people who prefer one or the other, and not clear to me whether one is dominant over the other among Clojurians.

andy.fingerhut08:12:51

If you can read and understand both without lots of head scratching, you will be better off reading and understand Clojure code of others.

brandon14914:12:28

Is there a slick way to remove the ns qualifier from a map where the keys are fully qualified?

manutter5114:12:31

The easy-but-dangerous way would be something like

(reduce-kv (fn [a k v]
                (assoc a (keyword (name k)) v))
              {}
              m)
but as soon as you have a map like {:company/name "Acme" :customer/name "Joe"} you’re going to have headaches.

brandon14914:12:05

My particular use case is to pull out table names from sql results so I can return them from a graphql api

brandon14914:12:49

I think it should be okay, because the schema should prevent situations like what you’re describing

manutter5114:12:57

I’d expect a lot of tables to have an id field, which is going to be troublesome.

manutter5114:12:25

The alternative approach is to use a rename table:

(def db->gql {:customer/id :customer-id
               :company/id :company-id})

 (defn fix-keys [remap m]
   (reduce-kv (fn [a k v]
                ;; Get the translated key, or keep the original key if
                ;; no translation is found
                (assoc a (get remap k k) v))
              {}
              m))

 (fix-keys db->gql {:customer/id 1 :company/id 2})
It’s not only safer, it’s a lot more flexible, at the cost of a bit more maintenance. But it does have the nice feature of passing the key through unchanged if it’s not a key that’s found in the remapping, so at least it’ll be easier to spot problems.

jumar20:12:58

isn't fix-keys just clojure.set/rename-keys?

manutter5120:12:38

By golly, you're right! I forgot that one was there.

manutter5120:12:02

clojure.set seems like an odd place to put a function that takes 2 maps and returns a map.

renato_ny14:12:12

where is a good place to start learning clojure??

renato_ny14:12:58

Thank you @manutter51 😃

manutter5114:12:55

There’s also https://purelyfunctional.tv/ if you like video-based learning.

eamonn.sullivan14:12:10

Hi again, still working with the real basics. I don't understand why: (:lst (:args {:args {:lst []}})) => []

eamonn.sullivan14:12:43

but ({:args {:lst []}} (->> :args :lst)) => nil

andy.fingerhut14:12:49

This expression is more similar in effect to what your first code example does:

user=> (->> {:args {:lst []}} :args :lst)
[]

eamonn.sullivan14:12:16

Ah, I'm missing the starting point in that expression, I guess. What confused me is ({:args {:lst []}} (->> :args)) => {:lst []}

andy.fingerhut14:12:18

Breaking down your second example, this sub-expression: (->> :args :lst) is equivalent to (:lst args) , which evaluates to nil

andy.fingerhut15:12:17

Clojure maps can be used as the first element of a list to evaluate, just like the name of a function, and they can be invoked like a function. They look up the first argument as a key, returning the corresponding value, or nil if there is no such key.

andy.fingerhut15:12:36

user=> ({:a 1 :c 3} :c)
3

andy.fingerhut16:12:51

It is very unfortunate that Slack munges the string ":b" in code blocks -- one of the most common keywords in example Clojure code!

eamonn.sullivan15:12:54

Thanks very much!

sova16:12:42

i want to build an e-mail server with clojure so that i can give all my users special @ourdomain.com email addresses. i figure a good starting point is looking for some sort of java mail server libs? any tips?

mruzekw17:12:25

Are you doing this just to learn? If not, most email providers allow the ability to use custom email domains

sova17:12:56

Well, partially to learn yes of course! and also i have a grander aim of incorporating this into the discussion board for the site, i think it could be a strong draw to have your own e-mail at our domain, but you're saying it's really complex and might just be worth paying for?

mruzekw17:12:34

If you’re doing it to learn, great! But it sounds like you might get what you want by building out the discussion board in CLJ(S) and using a service for emails

sova17:12:55

I would like to create an e-mail server for a larger project down the road. Perhaps now is not the time to learn about it. Do you know of any e-mail providers you could recommend?

mruzekw17:12:03

Are you looking to programatically send email? Or just give an inbox to users?

sova17:12:32

mainly just give an inbox to users.

mruzekw17:12:38

If you’re okay with Google, I’d suggest GMail. Other options might be http://protonmail.com or http://fastmail.com

mruzekw17:12:07

I use ProtonMail for my personal custom email

sova17:12:27

what if i were interested in building my own inboxes for users from srcatch

sova17:12:31

Well nevermind, it's not imperative

sova17:12:37

thanks very many

matt.wistrand20:12:07

I’m writing a test for a component and am using with-redefs to mock the underlying functionality invoked in the start lifecycle method. However, the return value also needs to have a .destroy method since that is called in the component’s stop lifecycle method. What is the cleanest way to mock this? I’m currently using deftype to create a mock return value, but have no clue whether that is good/idiomatic.

(deftype MockDestroyable [info destroy])
(deftest destroyable-thing
  (with-redefs [thing/creator (fn [& args]
                                (->MockDestroyable args (fn [] nil)))]
    ...))

noisesmith20:12:46

the premise of components is being able to replace things like with-redefs (which is unsafe even in tests, and definitely a bad idea in production code), with explicit arguments provided to the method

noisesmith20:12:27

you pass in a component providing the functionality it wants, with a stub function under the apropriate key (or implementing some protocol with a stub)

noisesmith20:12:46

you can use a plain hash-map with a function in it for this

matt.wistrand20:12:37

That implies that testing the components themselves will always involve un-mocked integration tests? For example, testing a component that starts a web server would involve creating a live server?

matt.wistrand20:12:19

For everything else, yes, I see the appeal of isolating side-effects in components and then passing them into the functions that need the context.

seancorfield20:12:06

@matt.wistrand If that matters for your test, you'd swap in a mocked component as a dependency when building your system under test.

seancorfield20:12:07

If your components are sufficiently granular, they will do just one thing, e.g., you'd have a web server component that only started/stopped a web server, and your application component would have a dependency on that.

seancorfield20:12:36

Or your application component could be wrapped with a web server component and so you could test the application component in isolation with no web server.

matt.wistrand21:12:26

For the system as a whole, I am mocking the individual components where needed. But for testing a specific component, I’m trying to discern the best way to write those tests.

matt.wistrand21:12:29

When testing my database component, for example, I actually am establishing a connection to a dedicated test database and then verifying I can perform operations against that database.

matt.wistrand21:12:38

Other components, however, wrap third-party APIs that I might not be able to integrate with during testing. So either I need to mock those calls, or not write tests for those components?

matt.wistrand21:12:41

Either way, I’m starting to get away from my original question of whether using deftype to mock something like a .destroy method (or .stop or .close) is a bad implementation.

noisesmith21:12:49

you can pass a hash map as a component dependency, the component lib does the right thing

noisesmith21:12:09

it has a no-op destroy

matt.wistrand21:12:40

Ah, didn’t know that. Thanks!

noisesmith21:12:54

I kind of made a mess of what I was saying, but this use case is pretty much the primary reason for component to exist

noisesmith21:12:00

so it's quite well supported