6 ms·
Could someone comment on what instances you would typically apply lambdas and closures in real-world code? I figure that they are at least more convenient than
by IvarTJ 12y ago
Could someone comment on what instances you would typically apply lambdas and closures in real-world code?
I figure that they are at least more convenient than callbacks with a *userData parameter like in C.
- edwintorok 12y agoAs argument to a 'map' function for example.
- agumonkey 12y agoEach time you want to write such callback, you'll need a special userData struct right ? otherwise you'll have a generic bag and lose typechecking. Let's say Closures are free typed anonymously defined structs.
- zem 12y agothe "with-" pattern (originally from lisp, i believe, but ruby did a lot to bring it to the masses), where something like a filehandle manages its own lifecycle, and calls your closure in between. so rather than the C-like let f = open-file-for-writing(filename); for line in array { write-line-to-file(f, line); } close-file(f); you can do with-open-file-for-writing(filename) {|f| for line in array { write-line-to-file(f, line); } } where the definition of with-open-file-for-writing() would look like def with-open-file-for-writing(filename, closure) { let f = open-file-for-writing(filename); call-closure(closure, f); close-file(f); } the benefit of having this be a closure rather than just a function pointer can be seen in the write array to file example above, where the "array" variable is in the scope of the calling function, but when with-open-file-for-writing calls your closure it can make full use of its own local variables.
- dllthomas 12y agoOf course, you can build your own closure: void do_stuff_with_file(struct relevant_data *, FILE *); ... { struct relevant_data data = { ... } with_open_file_for_writing(do_stuff_with_file, data, filename); } IMO, the biggest downside there being how far it typically pushes the definition of that function from the call site. Small functions - a good practice anyway - ameliorates that a bit.
- zem 12y agoyou can, but it's sufficiently clunky that it simply doesn't feel like a natural thing to do in the language. good language design is a lot more about the things it makes easy and natural than the things it makes possible.
- dllthomas 12y ago"you can, but it's sufficiently clunky that it simply doesn't feel like a natural thing to do in the language." It does to me, but I've done enough functional programming that I easily reach for concepts from that space. "good language design is a lot more about the things it makes easy and natural than the things it makes possible." Of course. I don't know where you got the idea I was saying closures aren't a good thing to have language support for. I said precisely the opposite.
- edwintorok 12y agoYou can also wrap the call-closure with an exception handler to make sure that 'f' is always closed when you leave with-open-file-for-writing.
- zem 12y agoright. and the beautiful thing is that once you realise that you only need to do it once, not everywhere you open, write to, and close a file.
- lostcolony 12y agoHere's an example I -just- had, actually, in production code (not in OCaml; below is pseudocode). It's not super powerful, but it made me happy because it turned what would have been a good 30 minutes to refactor and re-test into a quick 1 minute task. I had written a synchronous interface for some functionality, that had quite a bit of input data. It called an external web api twice, once to post some data, then a recursive check to periodically ping the API until some changes took effect (yes, none of this was ideal, but I couldn't change the API). I later realized that the code calling this interface needed to do some work in between these two calls. To refactor it into two calls would be a lot of work, and require a lot of book keeping, passing variables around or recalculating them, etc, and bloat the code. Instead, I just wrapped the second call in a closure, changing the interface; now rather than returning the result of that second function, it just returned that second function, which the calling code could invoke after it did its work. That is, I went from calling_func() -> Val = interface(); ... interface() -> ...//Do stuff to calculate vars do_work1(); do_work2(Var1, Var2, ...); to calling_func() -> SynchFunc = interface(); ...//Do whatever needs to happen between the two calls Val = SynchFunc(); ... interface() -> ...//Do stuff to calculate vars do_work1(); fun() -> do_work2(Var1, Var2, ...) end;
- lostcolony 12y agoI could also have done (provided I just needed side effects, not values) - calling_func() -> Val = interface(fun() -> ... end); interface(Func) -> ...//Do stuff to calculate vars do_work1(); Func(); do_work2(Var1, Var2, ...); to achieve the same effect, depending on how I want the interface to behave. I could also keep all existing calls working if my language supports multiple function arities, with interface() -> interface(fun() -> pass; end) or similar. The thing that closures give you, that I love, is that utility. I can minimally touch a function to inject entire chunks of functionality, without having to do major re-architecturing.
- orbifold 12y agoOne instance would be function composition. If functions are values in your language, you can define function composition in the language, that is given a function f : a -> b and g : b -> c, you can define their composition g . f : a -> c as g . f = \x -> g(f(x)) (Here \ denotes lambda) Why would it be useful to have function composition in your language? Well it gives you similar power as "method chains" in an object oriented language, without being tied to specific classes, especially if the language also supports polymorphic functions. It also interacts nicely with other abstractions usually found in functional languages: For example consider map, of Map-Reduce fame map : (Functor f) => (a -> b) -> (f a -> f b) then one has map (g . f) = map g . map f Now imagine that map would cause the function to be send to thousands of nodes in a cluster, then the above identity tells you that instead of doing that twice, once for f and once for g, you might aswell take g . f and send it out once. Also say you would for some reason know that f . g = id, the identity function, then map id = id, so you would not need to do anything. This might appear trivial, but if you can teach the compiler about those cases, you can do interesting stuff with it. In the case of GHC (the Glasgow Haskell Compiler), it is able to use such rules in its optimization phase, which allows people to write apparently inefficient but declarative code and let the compiler eliminate intermediate values. See for example https://hackage.haskell.org/package/repa https://hackage.haskell.org/package/repa.
- Dewie 12y agoWhy do you need lambdas/closures in order to have function composition? Don't you just need higher order functions? The thing about map id = id etc. probably has more to do with equational reasoning (can use equals to substitute terms, since there are no side effects, at least in Haskell), but I don't see the connection to lambdas/closures.
- lmkg 12y agoThe function returned by 'compose' is a closure because it captures references to its local environment (the two functions passed to 'compose'). If it did not close over these variables, it would not work. It might be possible to define a limited 'compose' operator in a language without closures that worked at compile-time/define-time, but you wouldn't be able to choose functions to compose at run-time like you could with a capturing 'compose.' Nitpick: Lambdas and closures are different things. A closure is a semantic notion of a function captures its local environment. A lambda is a mostly-syntactic notion of defining a function without giving a name. Whether a lambda is a closure depends on the language's scoping rules.
- tel 12y agoThis is hard to answer because an honest answer is "practically everywhere". First class functions, used properly, will take over every aspect of a program. Here's a neat example from a paper which tried to compare programming speed between functional, oo, imperative languages [0]. We'd like to build a "shape server" which allows you to build geometries of overlapping shapes and query as to whether a given point (in longitude/latitude) is covered by your shapes. The idea was to model a radar or early engagement system or something like that. The obvious way might be to build a whole nest of objects which communicate among one another to consider the formation of the geometry. Another method is to just use functions from points to booleans which model the eventual question "is this point covered". type Geometry = (Lat, Long) -> Bool type Radius = Double type Length = Double circle :: Radius -> (Lat, Long) -> Geometry circle rad (x0, y0) (x1, y1) = sqrt (dx*dx + dy*dy) where dx = x0 - x1 dy = y0 - y1 square :: Length -> Length -> (Lat, Long) -> Geometry square width height (top, left) (x, y) = y < top && y > top - height && x > left && x < left + width So here we build our geometry straight out of lambdas. A Geometry is just a function from (Lat, Long) to Bool and we generate them through partial application. We can also combine them union :: Geometry -> Geometry -> Geometry union g1 g2 pt = g1 pt || g2 pt intersect :: Geometry -> Geometry -> Geometry intersect g1 g2 pt = g1 pt && g2 pt minus :: Geometry -> Geometry -> Geometry minus g1 g2 pt = g1 pt && not (g2 pt) and then using all of these "combinators" build a sophisticated geometry which describes the final question "is a point covered by this geometry". The ultimate modeling tool was just lambdas. They are used so pervasively here I'd have a hard time pointing out each and every application. [0] The comparison itself is sort of stupid, but the paper is still neat http://cpsc.yale.edu/sites/default/files/files/tr1049.pdf http://cpsc.yale.edu/sites/default/files/files/tr1049.pdf
- ufo 12y agoOne simple use I like a lot is using tail recursion as a replacement for gotos. Its great for state machines and other "algorithmy" tasks. You get the benefits of gotos (the code you write is the same as the code you think) but the end result is actually manageable. http://www.lua.org/pil/6.3.html http://www.lua.org/pil/6.3.html Lambda the ultiamte goto: http://library.readscheme.org/page1.html http://library.readscheme.org/page1.html
- tjaerv 12y agoI have to confess to some surprise that this question is still being asked in 2014.