7 ms·
It's simple: the very concept of the program. I want to write a program that reads one text file, and outputs another. Let's say the first file contains a lis
by chris_mahan 12y ago
It's simple: the very concept of the program.
I want to write a program that reads one text file, and outputs another.
Let's say the first file contains a list of emails, one email per line.
The second file should contain the list of email domains in the first file, one line per email domain.
Procedurally, I would simply declare a list, read the file of emails, line by line, split each one on the @ symbol, and if the part on the right was not already on the list, I would add it to the list.
At the end, I would write the list to a new file, one line per item in the list.
How would one do that using functional programming?
- chris_mahan 12y agoBy the way, for those who think programs like that are too simplistic, and not a good showcase for all the FP goodness, maybe that's part of the problem. I work in the corporation, and I we do simple things.
- innguest 12y agoOk so here's one way to solve that problem in FP: We define what we have first (after all this is mathematics): EmailList = a list of Email Email = a string that goes "something" then "@" then "domain" Now what we want: DomainList, no duplicates. Ok so no we're set up. We want a function that takes us from an EmailList to DomainList, as defined above. There are no objects so we simply say the relationships between things. I will introduce the tools we need: map = takes a function and a list and applies that function to every element of the list, returning a new list. We use map over EmailList with the function extractDomain which we will write later (it's trivial to do without mutation so I won't show here). So now we have a list of domains, but there might be duplicates. So now I have DomainList(with duplicates) and I want DomainList(without duplicates), so I want a function from DomainList to DomainList. Well, what's the difference between what I have and what I want? What I have has too much, and I want less (ie. the list I have has too many domains (duplicates) and I want fewer (unique)). filter = takes a list and a function. That function it takes, that you will supply to filter, we'll call f. Function f takes one list element (so filter will call f for every element) and it must return a boolean to mean whether that element should go into the new list, or if it should not go into the list. So we could use filter to go from a DomainList with too much, to a DomainList with fewer things. However each time our function f is called, we don't know if this function has already been called before with that same domain; and because we don't have state to change a variable to keep a tally, it seems impossible. So let's approach the problem in a different way. One thing we could do is sort the list, then group adjacent similar items, then map over the list taking the first element of each item. But we won't do that. We need something more generic than filter. Let's look at fold. fold = takes a list, a function f, and a starting point z (that we call 'zero'). For each element of the list, it calls your supplied function f with the current element and the zero; but then, it would be useless because it would be the same zero all the time. So what fold does, to be useful, is that whatever the result of calling your function f with a list item and the zero is, THAT value (so the value the call to yourFunctionF(list_item, zero) returns) will be the next 'zero'. So now we have a little machine called fold that can thread something we have through its inner mechanism and play along with us. Using that we can say the zero is the list of elements we've seen so far. So now our DomainList -> DomainList function will be a fold. That fold will take a the DomainList we have, and a zero that will be the empty list (to start with!) and a function f. That function f will simply check if the item is inside zero. If it is, then simply return zero (because that will be the zero for the next call to f!); if it's not, then return a new zero with that item added to it. Now we've achieved the goal without any mutation (ie. without us talking/concerning ourselves with any mutation. of course there's lots of mutation going on in RAM but the point is not to eliminate that!) Does that help?
- chris_mahan 12y agoSadly, no. It's like I asked for a tuna sandwich and you explained how to build the engine of the fishing boat to go catch the tuna off the coast of Baja. I feel like I need a masters in computer science to understand what you wrote. let me see: # Python: domains = [] emails = open("file_with_emails.txt","r").readlines() for email in emails: ____domain = email.split("@")[1] ____if domain not in domains: ________domains.append(domain) out = open("file_with_domains.txt","w") for domain in domains: ____out.write(domain) out.close() input: joe@msn.com mike@aol.com tim@aol.com sam@hotmail.com output: msn.com aol.com hotmail.com Simple no?
- innguest 12y agoThat's actually not simple at all because it assumes an implementation, that is, a machine with RAM that changes. For instance, what does = mean between domain and email.split ? Who is equal to who? That question is a question that students sometimes have. You have to explain that Time is implicit in the code, and that an equal sign works by making the thing on the left equal to what the thing on the right was just before this line. So now there's a concept of Time and that's how things start to get very complicated, because it is implicit in your code. Then, what does ".append" mean? I mean, you call append, but now you don't assign the result to any variable? And what would it mean if you said domains = domains.append(domain), then we really need to have a talk about the hidden Time. So it only seems simple because it's familiar to you; so in fact I believe it's not simple at all. But this I believe it's simple, and my fishing boat engine above would've benefited from this: https://www.fpcomplete.com/tutorial-preview/4217/4k7kq6jGUE https://www.fpcomplete.com/tutorial-preview/4217/4k7kq6jGUE If you look at that code (you can also click play to run it) and then look at the explanation again, could you please tell me what doesn't seem simple? Edit: let's not forget your Python example uses a map, disguised as a for loop. That for is a keyword, but backstage it's just map where email is the element, emails is the list, and everything you write inside that scope is the function that gets passed the element each time (except in your case the function (i.e. the loop body) can also see the whole list, which is prone to bugs).
- 12y ago
- deadghost 12y agoUsing clojure: (ns my.namespace (:require [clojure.string :as str])) (->> (str/split (slurp "/home/deadghost/fake-emails") #"\n") (map #(second (str/split % #"@"))) set (str/join "\n") (spit "/home/deadghost/new-file")) Read entire file, split by newlines into a vector, split each element by @ and take the second part of each split, convert to set to remove duplicates, join set elements with newline and spit to new file. Clear as mud?
- raiph 12y agoOne way in P6: say ~ m/ '@' (.*) / for lines Ignoring the regex for now (the `/.../` bit), this reads as say (`say`) the string (`~`) that matches (`m`) for each of (`for`) the lines (`lines`). Note that lists (`lines` returns a list) are lazy by default in P6. So the above code will start producing results immediately and continue without exhausting RAM even if the input is infinite. The `/ ... /` bit above is a "regex". It means match the symbol '@' and return all the following characters of that line. P6 regexes are far more powerful than P5 regexes. P6 regexes can work together to comprise arbitrarily complex parsing grammars. The main P6 compiler, Rakudo, parses input source code using a P6 grammar. There are of course many more ways: say lines.map: { m/ '@' /; $/.postmatch ~ "\n" } In this instance `lines` is treated as an "object" (a `List` in this case) on which the `map` "method" is called. `map` calls the block of code (the `{...}` bit) against each element in its invocant, ie against each line. The block of code matches a '@' and returns whatever follows it ("postmatch") with a newline appended. (`$/` refers to "the match object"; it's one of three symbolically named variables in P6 as against the dozens in Perl 5.)
- innguest 12y agoWow, that looks like what was trying to come out of Perl. Very cool snippet!