13 ms·
The unreasonable effectiveness of declarative programming
- bollu 6y agoI wrote this to show off how to write a compact and powerful animation library. It turned out to be a nice case study in declarative programming as I wrote it! I'd love feedback on the API design, website design, and content as I'm trying to actively improve in all of these areas.
- dmix 6y agoThese are the type of blog posts I miss on HN. It used to be what was all over the homepage. Thanks for taking the time to write it. One bit of feedback: the sliders are almost impossible to click on with a mobile browser. The drag handle should be much larger. There’s also a horizontal scroll bar that appears partially above the bottom of the page, something is overlapping the main content body.
- RangerScience 6y agoVery cool. I overall very much like both your library, and your presentation. That said, I found the abbreviations initially confusing. For variable names and symbol/strings, maybe hold off until not the first example? For the functions themselves, can you do a non-abbreviated alias? I also missed the `par` in `seq((...).par(...))` the first time. And, personally, I think `par([...,...])` would be clearer, and allow for some beneficial constructs - is there a way to allow both? Particularly, I really admire the time travel aspects; that you can, what that means, and what allows that to happen. It reminds me a lot of what I've tried to do (not as elegantly, so I might come crib from you!) with the pattern renderer in https://github.com/rangerscience/butterfly https://github.com/rangerscience/butterfly
- jcelerier 6y agoNot that I want to disparage as the library looks neat and clear, but is that what declarative means in 2020 ? The code looks like mainly a builder pattern for an animation datastructure. I've always heard declarative as a sort of synonym to programs defined in terms of equational reasoning such as Lustre for instance - a rule of thumb to separate declarative languages from imperative languages is that in declarative languages x := 1 y := x + 1 x := 2 y will be 3 as "y := x + 1" must hold. e.g. it's not enough to just construct a graph-like datastructure for things to be declarative, the language has to do it itself. Also, note that the par / seq model (https://en.wikipedia.org/wiki/Series-parallel_graph https://en.wikipedia.org/wiki/Series-parallel_graph) is not as powerful as a general dag as things such as the K4 graph cannot be represented in it - even though it may make sense in some animation scenarios.
- wereHamster 6y agoIMHO in a declarative language your example should be an error. You defined x twice. Intentionally or not, this is confusing, for others and for you in a week when you have to look at the code again. Also, why should the second binding override the first? You introduced a temporal dimension where later lines of code somehow override earlier lines of code. That is not a necessity in a declarative language. It may be how we humans read text (from top to bottom), but it does not mean the computer has to process it in the same way.
- jcelerier 6y ago> IMHO in a declarative language your example should be an error. You defined x twice. Intentionally or not, this is confusing, for others and for you in a week when you have to look at the code again. c'mon, this is an example in a forum post ! of course it doesn't make sense in real life. real life is distance := computeDistanceToCenterOfScreen(cursorPosition) text := "the current distance is: " + distance but that just muddies the waters needlessly > Also, why should the second binding override the first? You introduced a temporal dimension where later lines of code somehow override earlier lines of code. yes, some languages work like that, but it's not the point - either you assume that you can update values in your declarative bindings like that, or you have to define your binding in terms of some external behaviour that will change in magic ways, which again would not be very nice to do as a simple forum post example
- YeGoblynQueenne 6y agoThank you for the interesting post. Can you explain why the "A complex animation" code example is declarative? I can see that the names of the functions are declarative in style but the code is a series of function calls that seem to be giving instructions to the javascript interpreter to perform a sequence of operations. For example, you describe the code as follows: 1. anim_const(name, val) to set a constant value val to name name. 2. anim_interpolated(ease, name, val, time) to change to a named value with name name to value val in duration time. 3. anim1.seq(anim2) to run anim2 once anim1 has completed. 4. anim1.par(anim2) to run anim2 in parallel with anim1. 5. anim_delay(time) to do nothing for time time. All these function calls are described by verbs like "set a ... value", "change to a ... value", "run", "do" etc. The way I understand declarative programming (I do most of my coding in Prolog these days) it means that one declares what is the state of the progam, rather than describing what steps should be taken to change it. But your code seems to be describing steps to change the state of the program. It is "do this, then do that" style of coding. Do you mean something else by "declarative"? Could you say what you mean by "declarative"?
- jerf 6y ago"Declarative programming" at its core seems to be about separating what the code says from what it does, and trying to put more "magic" in between the saying and the doing. But that's not a binary distinction for all kinds of reasons. What a code "says" is already a bit subjective to start with, but then we get into things like, is this "declarative"? def SayHello(): print("Hello!") SayHello() After all, when I type "SayHello()", I'm not specifying how I want Hello to be Said, just that I want it said. Yet, of course, if this is called "declarative" than the word is stripped of all meaning, and we do clearly want it to mean something. Personally I'd set the line as being somewhere where by definition of the system, there is a barrier set up and you are no longer allowed to "see into" the implementation because it's going to do things so crazy to your declaration that allowing you to see into it would ruin it. e.g. in SQL when you send in your query the query engine is reserving the right to rewrite it in absolutely crazy ways, and in principle, you have no right to ask about what it actually did as long as you get the "right answer". This is because I then like to segue into how that barrier is somewhat illusory because it's always going to take some concrete actions and you may have need to see into those actions. But that isn't the only place to draw the line, it's just where I find it convenient to draw the line so I can ride a pet hobbyhorse. There isn't really a bright line, even though there is clearly some real difference we are trying to talk about.
- imglorp 6y agoI appreciate the simplicity and visual style of the blog.
- effie 6y ago> website design I like the font used for the code, very readable. But underscoring keywords is superfluous and makes the code listing weird - bold font is enough. Also, the line numbers are jarring, they are too pronounced and they are not important to the reader. Maybe make them gray or put them on the right-hand side. This is a common problem with many blogs. I don't care about the line numbers! The main text is hard to read - this font, a kind of an hybrid of serifs and tall quasi-monospace characters, is weird and hard to read. Try more usual sans-serif font with shorter-height characters, I think it will be much better both for readability and more distinct from the code listings. The paragraph symbol is obsolete, I would lose that. At least don't make it look like a link, nothing happens when I click it.
- agys 6y agoSmall typo: On being invoked, it sets out.v = field should be: (…) out.field = v
- carapace 6y agoI really like your website design, very clean and readable without sacrificing style. The library is very nice too. The only thing I would add is, uh, rub some Category Theory on it. It seems like you could "go all the way" and define a formal mathematical semantics for animations, which would be nice. VPRI STEPS project made a quasi-mathematical language called Nile for compositing 2D vector graphics ( http://www.vpri.org/pdf/tr2009016_steps09.pdf http://www.vpri.org/pdf/tr2009016_steps09.pdf ) that might be something to look at.
- one-punch 6y agoI believe this library would be very useful for simple animations. The small size and simple API should make this usable in many cases. As for more sophisticated animations, I can think of reanimate [1], which outputs animations in SVGs, and should work on the web with wasm if integration is needed [2][3]. I completely agree that declarative programming (and functional-style programming) shines for composing animations. Imperative programming does not show the intent as clearly, and makes it hard to reason about or time travel animations. [1]: https://reanimate.readthedocs.io/en/latest/glue_tut/ https://reanimate.readthedocs.io/en/latest/glue_tut/ [2]: https://github.com/tweag/asterius https://github.com/tweag/asterius [3]: https://github.com/Lemmih/reanimate https://github.com/Lemmih/reanimate
- gherkinnn 6y agoInteresting read. Until safari on my iPhone 7 crashed. Could it be a memory leak? Or simply too many animations for my ageing phone to handle?
- augustk 6y agoFor me animations mixed with text is bad UX. Would be better with buttons to start the animations when you have read the relevant paragraph.
- reuben364 6y agoBasically auto playing anything for me is bad UX.
- vanderZwan 6y agoThe website uses SVGs for animation instead of the canvas2d API, I'm fairly certain that's the cause. (honestly I love SVGs for static images or at most one or two animations when used on a web-page, but if you're not using canvas2d or even webgl with a thin shim for anything more complex than that, mobile users will suffer)
- baybal2 6y agoI don't like weird sounding headlines. Why they keep inventing them?
- kupopuffs 6y agoWhat's wrong with it? Besides maybe not knowing these words
- protomolecule 6y ago>I don't like weird sounding headlines. Why they keep inventing them? It's the opposite -- they are reusing the old ones. https://en.wikipedia.org/wiki/The_Unreasonable_Effectiveness_of_Mathematics_in_the_Natural_Sciences https://en.wikipedia.org/wiki/The_Unreasonable_Effectiveness...
- ggambetta 6y agoBack in the day when I was making videogames (http://www.mysterystudio.com http://www.mysterystudio.com), I implemented something similar for my framework. Most of the framework was declarative. There were Sprite objects that had an associated Image and a position (among other things). The onUpdate(dt) method of the "screen" didn't explicitly draw things on the screen, it only updated positions and other attributes of the Sprites, and the renderer would do the rest (back in the days of software rendering, it updated only dirty sections of the screen, etc). Still, making programmatic animations was difficult, and for videogames you want things to look very nice (smooth movement, easing in and out, transparency effects, that kind of thing). There was a lot of { srcX, srcY, dstX, dstY, time } sets, for every thing that moved. At some point I had an idea similar to what this blog proposes. I called them SpriteControllers, and they could be "attached" to a Sprite. The engine would call each SC's onUpdate(dt) method on every frame, and the SC would change the position, rotation, alpha, or tint of the Sprite. By default all the attached SCs would work in parallel, so the next step was to have a SCSequence to run things in series. Also each SC was allowed to declare when it was "done". Some did (like linear motion from A to B), some never finished (like a pulsating glow). It was a fun development in the sense that it made something that was very difficult to do by hand surprisingly easy, and took our games to the next level in terms of visual quality, just by adding a bunch of very short classes. Making games was fun in a way. Alright, back to work. These protos aren't going to copy themselves into other protos by themselves!
- OliC 6y agoMy friends and I love playing "Murder, She Wrote" on a big TV screen during downtime at LAN parties. We streamed it on Twitch for a while. We're big fans, great work!
- MaxBarraclough 6y agoI was expecting an article on the topic of declarative programming. Instead, it's about a JavaScript animation library.
- deleted 6y ago[deleted]
- reuben364 6y agoIn Haskell, one can make more general combinators in the following way: type Anim a = (Duration -> a, Maybe Duration) -- Linear interpolation linear :: Anim Duration linear = (id, Nothing) -- Sequencing seq :: Anim a -> Anim b -> Anim (Either a b) seq (f, Nothing) g = (\t -> Left $ f t, Nothing) seq (f, Just df) (g, dg) = (\t -> if t < df then Left $ f t else Right $ g t , df + dg) -- Parallelism par :: Anim a -> Anim b -> Anim (a, b) par (f, df) (g, dg) = (\t -> (f t, g t), max df dg) -- Constant pure :: a -> Anim a pure a = (const a, Nothing) -- EDIT: forgot delay delay :: Duration -> Anim () delay d = (const (), d) I know there's an Applicative in there, but I'm not sure where else this lies in the typeclass hierarchy. EDIT: More fixes EDIT: I should really test in GHCI before I post these things. But I'm lazy.
- quickthrower2 6y agoThat looks neat. Making me fall in love with Haskell again which I want to avoid as I can’t use it at work ;) You could add a monoid instance which would be useful (for one example) if the animation produces a matrix transform then you could combine two animations that produce transforms. For example a rotation and a move. I’m sure there are other good monoid animations. Strings would be a simple example I guess.
- uryga 6y agopedantic stuff: typo here: seq (f, Maybe df) (g, dg) t s/Maybe/Just/ and `par` should be par :: Anim a -> Anim b -> Anim (a, b) par (f, df) (g, dg) = (\t -> (f t, g t), max <$> df <*> dg) --- i'm not sure about `par` - if my understanding of Anim is right, it'll take the shorter animation over its specified duration. what if it was something like: par a@(_, da) b@(_, db) = (ab, max <$> da <*> db) where ab t = (a `at` t, b `at` t) at :: Anim a -> Duration -> a at (f, Nothing) t = f t -- freeze when t exceeds dur, i.e. the animation is done at (f, Just dur) t = f (min dur t) might be less elegant but i'd expect the combinator to respect an animation's duration. or is that not what the duration represents? i.e. is this correct: stretch :: Float -> Anim a -> Anim a stretch factor (f, dur) = (\t -> f (t / factor), (factor *) <$> dur)
- rkangel 6y agoInterestingly, if you watch the Erlang: The Movie (https://www.youtube.com/watch?v=BXmOlCy0oBM https://www.youtube.com/watch?v=BXmOlCy0oBM) they use the term 'declarative programming' to refer to what we would now call 'functional programming'. It's an interesting way of thinking about it - these sort of declarative interfaces are very simple in languages with first class function support.
- tsimionescu 6y agoC has first-class functions support, but I don't think it's very easy to define such interfaces in C. Closures and automatic memory management seem to be the "magic dust" that makes this nice to use, first-class functions are necessary but not sufficient.
- lmm 6y agoC doesn't have first-class functions, because you can't define new functions in general places (only at top level).
- tsimionescu 6y agoThe definition of first-class functions is the ability to treat functions as data, which C supports. Yes, they are cumbersome to work with, since they must always be defined at top level, but that is just missing syntax sugar. GCC even allows nested function definitions.
- dllthomas 6y agoMy understanding agreed with your respondent. I haven't dug deeper but per wikipedia there's a split in the CS community as to whether function literals are necessary for functions to be first class. I think we all agree on the reality of the situation, whichever way we decide that ambiguity.
- lmm 6y ago> Yes, they are cumbersome to work with, since they must always be defined at top level, but that is just missing syntax sugar. It's not just syntax sugar. Try writing a function that takes an array of integers and an integer x, and sorts the array mod x by calling qsort. In C this is not just cumbersome but impossible.
- verroq 6y agoThe unreasonable effectiveness for toy examples to ignore real world complexity.
- andybak 6y agoYou can surely do better than drive-by snark? Where do you feel this is likely to break down? What would a better approach look like? Is it intractable or can one have both simplicity and power? I suspect the answer is "yes" in which case OP deserves a better response.
- throwaway55554 6y agoA pity the author didn't provide you with a more professionally polished version you could add to your github and call your own.
- carapace 6y agoTwo snarks do not make a witticism.
- serpix 6y agoUnsolicited code review regarding code readability: If you write a comment saying x is y. Then rename x to be y. The comment adds a layer of abstraction. Now every time you encounter x you refer to the table of abstractions to get to y. In a similar way the library has shortened function names. This is another layer of abstraction so you need to decompress the shortened function name once again.
- andybak 6y agoIf you want to show off how simple your API is then I can think of several things that would improve clarity: 1. Don't use parentheses in comments. They make things more visually confusing when javascript imposes enough syntactic clutter as it is. 2. What's with /* */ ? Doesn't js allow keyword arguments? 3. Your method names are probably a little short for my taste. I'd be happier with sequence() and parallel() instead of seq() and par(). Par especially seems a little too obscure when first encountering it. 4. "cx = location | cr = radius" - why not allow both forms? - or just the clearer one rather than the shorter one.
- gcb0 6y ago> 2. What's with /* */ ? Doesn't js allow keyword arguments? No.
- andybak 6y agoI couldn't remember if that was one of the things they fixed with ES6. I wonder if passing in a dict is a good alternative in this case. More syntax clutter but at least the params have some semantic meaning.
- randallsquared 6y agoObject destructuring in the parameter list comes very close to supporting keyword arguments, with only a couple of characters overhead.
- carc1n0gen 6y agoRe: #2, it does not
- bmn__ 6y agoThread neighbour gcb0's answer is censored by Hackernews. (Turn on the showdead setting in your profile to see it.) I want to answer, but I can't attach directly to the hidden post, so I have to go up one level. ---- > 2. What's with /* */ ? Doesn't js allow keyword arguments? It does since version 6. Example: The function call anim_interpolated(ease, name, val, time) should be rewritten thus: anim_interpolated({ease, name, val, time}) The corresponding function definition should be rewritten thus: function anim_interpolated({ease, name, val, time}) { … }
- julius_set 6y agoYour article reminds me a lot of Brandon Kase’s talk regarding Algebriac animations written in Swift Highly recommended watch: https://youtu.be/dyiLLdkzzRc https://youtu.be/dyiLLdkzzRc
- EGreg 6y agoFor me, there are only two benefits to declarative programming: Safety and quick Understandability of code. I would say that markup languages fall into this category (HTML, Markdown etc.) Everything else is better to be done by some sort of imperative language. The declarative stuff can be a subset and a convention but you can always break out of it.
- carapace 6y agoI see it almost the opposite way: now that the machines are so powerful you should prefer the higher-level declarative languages (like Prolog!) unless and until you need the efficiency that imperative languages can unlock.
- gitgud 6y agoA great example of the power of [1] fluent API's (aka The Builder Pattern). Basically, method-chaining allows you to concisely express programs, by hiding irrelevant complexity behind abstractions. [1] https://en.m.wikipedia.org/wiki/Fluent_interface https://en.m.wikipedia.org/wiki/Fluent_interface
- jschwartzi 6y agoQt with QML does exactly this with animations. It works very well. You don’t really need a functional language to do this kind of thing. I’ve written classes in C++ which are composed to produce different behaviors. Composition in OO is a way to accomplish this without using a functional language.
- BiteCode_dev 6y agoDeclarative is great, and I wish more people created clean declarative API with regular languages instead of writting yet another DSL. But remember that the problem with a declarative syntax, is that it needs a runtime, which typically the end user doesn't touch. And if the runtime doesn't take into consideration one use case, the user is stuck. Don't forget to provide an escape hatch.
- mrfredward 6y agoThe other problem with declarative code on a runtime you didn't create is traditional forms of debugging go out the window. You can't set a breakpoint, or single step, or even printf debug declarative code directly, though you can peek behind the scenes a little if you control the runtime.
- BiteCode_dev 6y agoYeah, you may debug the result of your declarations if introspection is ok, but god has mercy if the error is at the runtime level.
- klysm 6y agoDeclarative code doesn’t need a runtime if you have a compiler
- squiggleblaz 6y agoEven if the language is compiled, you still need a runtime. GHC compiles to native code, but there's still a runtime for instance to handle the garbage collection, to sort out the lazy evaluation, and to translate the blocking IO API at the haskell end into a non-blocking IO api at the kernel end. I surely wouldn't want to be debugging my haskell code on a time budget by directly looking at the machine code that is running. I know some people do that and it can be quite illuminating, but the fact that I can debug haskell in its own terms is good.
- kondu 6y agoYou're conflating runtime with interpreter. Interpreter translates non-native code to native code, a runtime is what gives you garbage collection, the vm(ex: JVM), std libraries, etc.
- mD5pPxMcS6fVWKE 6y agoIn non-declarative way it would look shorter and clearer anim_set("cx", 100); anim_set("cr", 0); anim_interpolate(ease_cubic, "cr", /val=/10, /time=/3)); etc don't know what are you trying to achieve here
- chmod775 6y agoFirst off, this part: >anim_interpolate(ease_cubic, "cr", /val=/10, /time=/3) is still declarative. In any your example is incomplete and proves nothing because the end result should be a function that takes t and returns cy and cr. Not sure what your code is supposed to be really. Secondly, the author explained at length what the advantages of their approach are (purity, composition, time travel debugging, ...). I'm not sure what you're trying to achieve with your comment except trying to make yourself look smarter by deriding others.
- mD5pPxMcS6fVWKE 6y agoYou confuse declarative and functional. Declarative is when the sequence of execution is not related to the order of operators in the program text, but is derived at run time. The animation, where sequence of events is known exactly, is the poorest example for declarative programming.
- chmod775 6y ago> You confuse declarative and functional. No I'm not. Also functional programming is declarative programming (but not the other way around). If your code is supposed to be functional, it is also declarative. I was trying to freely guess what your incomplete example was supposed to do. Maybe I was wrong, but then you didn't provide much to work with. > The animation, where sequence of events is known exactly, is the poorest example for declarative programming. This wasn't a competition to come up with textbook examples of declarative programming.
- cool-RR 6y agoLooking at the first code sample, I wouldn't call it declarative at all. For me, the defining feature of declarative code, is that it doesn't have a list of actions to be performed one after another. That code sample is such a list of actions, which for me makes it imperative code, meaning "first do this, then do that, then do the other thing." The "list of actions" approach is what makes code complexity grow exponentially, because whenever you look at a series of 10 instructions, you have to think "what state was created by the combination of the first 7 instructions, and does the eighth instruction depend on that state?"
- Trufa 6y agoSo you would call: arr .map(x => x + 2) .filter(x => x % 3) .map(x => other(x)) Imperative?
- nevir 6y agoYes. That is imperative. Declarative code describes the _output_ or final desired state of something.
- hyperpallium 6y agoIs f(g(x)) imperative? There's an ordering... and it works in terms of mappings, not outputs...
- lhorie 6y agoDepends on what language you're using. In math notation, given `y = x * x`, you can work backwards from `y = 4` to figure out the value of x, whereas in, say, Javascript, `y = x * x` means exactly "compute y as the value of x times itself" and only that. For illustration, we could also compute the square of x in a different imperative form, e.g. in terms of a loop over additions. Similarly, in mathematical notation, `f(g(x))` can be a way of expressing the existence of some sort of law, e.g. maybe f and g are commutative. That means that if code were written as such in a 5th-gen language[1], the underlying engine is free to recompile the code into `g(f(x))` assuming the commutative property holds and the performance is better. By contrast, in a imperative language, `f(g(x))` generally would compile to that exact order of operations (unless you have a mythical sufficiently smart compiler) I can see an argument about JIT compilers being smart in some cases, but the philosophical distinction between imperative and declarative paradigms is that with declarative style, the compiler can transparently swap units of arbitrary complexity. For example, given some CSS rules, a browser engine can decide to paint the screen buffer however it wants, be it top-to-bottom, edge-to-center, layer-over-layer, etc regardless of how the CSS was originally expressed. [1] https://en.wikipedia.org/wiki/Fifth-generation_programming_language https://en.wikipedia.org/wiki/Fifth-generation_programming_l...
- antirez 6y agoDeclarative would be more like: circle is at position 10,10 at time 1.5 circle is of size 50 at time 5 circle is at position 20,50 at time 3 And so forth. And because of this statements, an animation is computed that respects the declarations.
- jpxw 6y agos/refrentially/referentially/g Very cool though!
- kerkeslager 6y agoAs others have pointed out, I wouldn't necessarily call this "declarative programming"--this actually is more what I'd call "literate programming". That's semantics, but I will add that in my opinion, literate programming is way more effective than declarative programming. Literate programming is just syntactic sugar around functional programming. For example: qux(bar(foo,baz),garlply) ...becomes: foo.bar(baz).qux(garply) This is the sort of thing that typically emerges when you have immutable objects, and demonstrates a sort of equivalence between immutable OOP and FP. This is why I don't generally care about functional programming versus object oriented programming debates: they're equivalent if you don't mutate. I'm much more interested in immutability than a slavish loyalty to functions over methods. And I tend to agree with the OP that literate programming is very effective. What people usually mean when they say "declarative programming" is they want to write a config file in, say, JSON, and have that be their program. But that would mean that the implementer of the language would have to think of every possible way that you could possibly want to configure the program, so they start adding customization points where you can write code in an actual language which is called in certain spots. So now you have to know how to program in a normal language, and know how all the different customization points work. Oh and while you're doing that, you can forget about getting anything helpful like a stack trace, because it was declarative so you don't need to worry about what is calling what, right? So you get things that just fail silently and you don't know why, like: class Mail(models.Model): ordering = '-received' sender = models.EmailField() receiver = models.EmailField() received = models.DateTimeField() body = models.TextField() This orders by received ascending, even though you clearly are telling it to order by received descending... have fun figuring out why! I'm picking on Django here but it's actually one of the best examples of declarative programming. The problem isn't that they didn't validate for this case: I don't think they could do that reasonably. The problem is that it's not really possible to implement declarative programming in a way that catches all the possible errors of this sort.
- FearNotDaniel 6y ago> As an example, a staggered animation is a nice way to make the entry of multiple objects feel less monotonous. Hopefully readers considering this sort of thing for UI transitions will bear in mind: this is nice if the goal of your application is entertainment, deeply irritating if the goal of your application is productivity.
- landryraccoon 6y agoI'm curious about how one is supposed to reason about the time and space complexity of declarative programs. I don't work in a realm where CPU and memory costs can be assumed to be infinite (or even cheap). How do declarative programming paradigms typically offer guarantees or bounds on computation cost? I'm assuming that any declarative language powerful enough for general use is expressive enough to represent an NP-complete problem; i.e. : Find a set of booleans X1..XN that satisfy Y logical statements (or prove that there is none possible). Therefore absent opening up the "black-box" of the language, the time and space complexity could be unbounded. With an imperative language, of course, since the programmer is probably specifying the exact order of operations and data structures being used, reasoning about and bounding the cost is usually straightforward.
- moljac024 6y agoQuite simple: you don't. Time and space complexity is an implementation detail, if you care about those things then declarative programming is out.
- jzcoder 6y agoYou should look at Actions in the Python implementation of Cocos2d. It uses operator overloading to allow '+' for sequential actions and '|' for parallel actions. These are composable and reversible for quickly creating complex animations. http://python.cocos2d.org/doc/programming_guide/actions.html http://python.cocos2d.org/doc/programming_guide/actions.html Example: bounce = Jump(150, 0, 4, 2) scale = ScaleBy(2, duration=1.5) rot = RotateBy(360, 0.80) scale_and_rot = scale | rot bounce_in = bounce | scale_and_rot bounce_out = Reverse(bounce_in) logo.do(bounce_in + bounce_out)