5 ms·
pg quote from the article: you don’t just write your program down toward the language, you also build the language up toward your program. As you’re writing a p
by AndrewHampton 16y ago
pg quote from the article: you don’t just write your program down toward the language, you also build the language up toward your program. As you’re writing a program you may think "I wish Lisp had such-and-such an operator." So you go and write it. Afterward you realize that using the new operator would simplify the design of another part of the program, and so on.
I remember thinking when I first read through pg's essays a few years back and again now that you can do this in declarative languages as well using functions.
For example, my programs often looks something like this:
main(){
var data = getData();
pushData(data);
}
function getData(){
var data = fetchDataFromSource();
var errors = validateData(data);
if(errors != null)
//handle errors
return processData(data);
}
function fetchDataFromSource(){
...
}
function validateData(var data){
...
}
...
Could anyone explain why functional languages are better for this? It seems to me this is the same as "building the language up to your program".
- jshen 16y agoYou're example is too small for the differences to become clear. There isn't any OO in your code for example.
- jshen 16y agoI can't edit my comment anymore, but I wanted to add some more detail. Building larger programs is about clean abstractions. What your doing only needs basic functions for abstraction, but when you try to do something larger you'll begin to need/want more ways to abstract your code. Then the differences will become clear. But, you punted by not actually handling the errors. As others comments show, your example may need more abstraction tools once you try to handle the errors :)
- joevandyk 16y agofor example, getData() could be written something like: getData = fetchDataFromSource . validateData . ProcessData See http://www.haskell.org/haskellwiki/Function_composition http://www.haskell.org/haskellwiki/Function_composition
- lzm 16y agoHow would you handle validation errors in this case?
- lallysingh 16y agoTwo ways: (1) (avoid) something similar to exceptions; (2) (much better) your datatype has an 'invalid' value. See Maybe in haskell, or Option in Scala (I think that last one's right...).
- eru 16y agoWith error handling in Haskell your code would look like getData = fetchDataFromSource =<< validateData =<< ProcessData By the way, the original getData with (.) is backwards. It should be: getData = ProcessData . validateData . fetchDataFromSource
- CountSessine 16y agoIt would be specifically handled by the return type from validateData and the argument to ProcessData. It sounds clunky, but with an algebraic data type it would be fairly clean - just another pattern to match in ProcessData. For a simple example like this, that might be the cleanest solution. For anything more complicated (like recursive descent parsing), a processing chain like this is an ideal application for a monad, where the binding between functions would be customized to avoid calling ProcessData altogether.
- deleted 16y ago[deleted]
- ww520 16y agoCan you just use a callchain? function getData() { return processData(validateData(fetchDataFromSource())); }
- jerf 16y agoIt's not quite equivalent due to differences in the semantics; a functional language can far more easily do loop fusion: http://en.wikipedia.org/wiki/Loop_fusion http://en.wikipedia.org/wiki/Loop_fusion . An imperative language is likely to not even try in that situation, or in the case of a good C compiler, end up failing to do the optimization at the slightest hint of pointer use. In Haskell it's robust and can be composed many times.
- seiji 16y agoLook at all the duplicated code in this python redis client: https://github.com/andymccurdy/redis-py/blob/13850b1b9ed34ee69b37a665b526ddea18860025/redis/client.py#L852 https://github.com/andymccurdy/redis-py/blob/13850b1b9ed34ee... . The python version has duplicated 'self' tokens, 'def' tokens, arguments, execute_command, converting keys in each function, ... Compare to the non-duplicated code in my Lisp redis client: https://github.com/mattsta/er/blob/681e4f91d0601d2997b2b5b7aad628f9b972595c/include/redis-cmds.lfe#L199 https://github.com/mattsta/er/blob/681e4f91d0601d2997b2b5b7a... . In the Lisp version, each redis-cmd-* call is a macro created by a macro which calls another macro to create the function in a polymorphic way. You are one step closer to Lisp enlightenment.
- BrandonM 16y agoTo be fair, you could have done much the same thing in Python: basic_execute_command = { # snip ### SET COMMANDS ### "sadd": "Add ``value`` to set ``name``" , "scard": "Return the number of elements in set ``name``" , "sismember": "Return a boolean indicating if ``value`` is a member of set ``name``" , "smembers": "Return all members of the set ``name``" , "smove": "Move ``value`` from set ``src`` to set ``dst`` atomically" , "spop": "Remove and return a random member of set ``name``" , "srandmember": "Return a random member of set ``name``" , "srem": "Remove ``value`` from set ``name``" # snip } def _basic_execute_command (self, cmd): return lambda *args: self.execute_command(cmd.upper(), *args) # snip def __init__ (self, *args, **kwargs): # snip for cmd in self.basic_execute_command: self.__dict__[cmd] = self._basic_execute_command(cmd) # snip This is pretty memory-inefficient if you have several redis objects, but I'm sure you could come up with something better than what I hacked together in a few minutes. I know that Lisp ranks above Python on the metaprogramming scale, but simply adding easy object introspection can allow for cool factoring constructs that a Java (or similar) programmer wouldn't even consider. It's possible to make copy-pasta in any language.
- silentbicycle 16y agoYeah, if you can interpret a data structure at runtime, you don't need to write out the corresponding code at all. You could have all the redis connection instances share the same command dict, or make it a variable in the package.