This may be of interest: I just came across https://github.com/Tieske/Penlight, which includes some functional programming idiom support.
last year we had a long conversation about adding penlight… and luarocks in general.
main takeaway was: with luarocks, we’d have a hard time adding yet another project management layer so that community scripts could all work.
penlight has some nice features, but also some incredibly heavy ones… which could introduce quite a lot of confusion and complexity. so we sortof punted, opting not to get into it. @zebra @ngwese
i suppose we could re-open that inquiry, keeping in mind that most of us have focused areas of the code we’re already working on.
Yeah… where to draw the line. Penlight looks impressive, but also quite heavy, and part of the appeal of norns for me is that it’s carrying a payload which does what’s needed to provide an integrated environment and nothing more - it’s lean.
Agree. The question that needs to be asked is ‘what problem are we solving?”
The ‘issues’ I have had are: clock sync/link - now solved, can’t be bothered writing things over & over - mostly output - I have my own solution for that. I also wondered today if some thing like teletypes faders & buttons might make sense? Params could do dynamic updating for slightly nicer UX
And that’s about it. Don’t need a lot else. All that is Norns specific too
Especially while nothing’s really stopping a script author from pulling in penlight to their script if they’d really like its features
Adding stuff to the base software stack is much easier than removing it so there has naturally been a healthy amount of debate around adding dependencies like
penlight or even leveraging
luarocks. I look at stuff like
penlight and think:
- is this something we want to carry forever as a dependency?
- does it promote / foster implementations that are liable to work well in the embedded lua environment which
- does it provide something which cannot be achieved easily or by other means?
- is it something that helps or hinders someone new to the platform or to coding?
- is it something which helps or hinders one’s ability to share scripts among the community?
- is there any risk that adding the dependency could increase support load on the core collection of folks fostering the platform today.
I’ve personally gone back and forth on this several times while debating myself. In the end I usually found myself coming back to:
penlight to me looks like a mixed bag, some good, some not so good. The higher order functions are the few things that I could see myself using but those are also readily had with passing functions/closures into existing iteration mechanisms. I’m generally a fan of functional programming but I also expect/want a solid immutable collection library to go with it and an optimizing compiler to eliminate all of the intermediate garbage when using it in an environment which is soft real time.
Of late there has been more energy around building/expanding the core library in ways which are broadly applicable across the types of scripts people are generating.
All that said I do occasionally install stuff locally just to try it out and experiment (or pull it into the
lib/ space for whatever script I’m working on). For code/scripts which one doesn’t intend to share there is little downside.
Ah, the MIT disease of incorporating every feature and option. Perhaps we should be thankful that the norns cannot (yet) function as an email client.
The lowest-impact route for new code is is probably to keep letting people roll stuff out as top-level script libraries. Scripts can refer to one another, and the main loadable script can serve as a demo. (The script downloader doesn’t do dependency analysis, but taking that task on probably means, well, reimplementing luarocks.)
This is a bit of an aside, but: as a very minor feature upgrade, would there be any support for something like
.gitignore for sub-directories of scripts? I want to roll out a script which itself scans a subdirectory for other scripts, none of which is directly runnable. Right now everything appears.
Alternatively - house style? - should everything actually be somewhere under
lib? As I think about it, I’m not averse to
lib/something for “internal” coding, so may just refactor to that.
How have others approached saving “instances” of objects in their norns scripts?
tabutil.save doesn’t save the metatables. Could I use
tabutil.load and then set the metatable of the loaded table?
There’s probably a better way to do it, but what I’ve basically done is to have my class constructor take a table of options that set all of its properties. When I save it, I use tab.save, and when I load it, I pass the loaded table to the constructor again and overwrite my instance with a new one.
Sounds convoluted as I type it, but hopefully makes sense…
sort of like:
local instance = MyObject.new(options) tab.save(instance, path) local loaded = tab.load(path) instance = MyObject.new(loaded)
matron menu system code and
maiden share an implicit agreement that “runnable” scripts are anything
*.lua that appears just below the script/project dir which is not below
If you have lots of code which isn’t directly runnable I’d recommend adding structure under
lib/. I do this quite regularly and it is the strategy in use by many of the more complex scripts.
Makes sense, will do - thanks.
also, ‘data’ and ‘docs’ will not be scanned by menu
makes sense: permissive languages go hand-in-hand with Slightly More Testing (but type-safe/immutable languages aren’t necessarily non-tests-required).
I am interested in this thread, primarily as a programmer, but also seeing what peoples’ perceptions of paradigms and patterns are, as well as their understanding of them. It varies dependent on prior experience, skill level, culture, and many other things, and that’s useful stuff to process.
- “house style” is at best advisory unless you have either good tooling (eg a linter or formatter or both), or a language where there’s One Way To Do It (eg Python).
- without Magic Tools (and languages that easily support them) making sure that a few Good Examples already exist is always helpful.
- (as @cassiel points out, the maiden UI is one such tool, thumbsup; at the very least, new coders have access to a UI with formatting and such.)
- strongly agree with “the most straightforward thing to write is a list of imperative actions”. I like that we have a REPL, on device, that does things; I like that you can bang out the ugliest script that Works For You, because it’s a machine to make art, not ecommerce stores. And then, if you want more structure/tidiness, it is available in a variety of ways
- as long as the internal libraries are consistent, that’s a good start. any behaviour/patterns enforced there will probably trickle down a bit.
I’m always interested in watching OOP/functional arguments because it takes me a while to cotton on to what people are really advocating for/complaining about. So: I’m largely an OOP programmer at heart (and by trade), but when I say that, I’m saying it as a Rubyist, and my OOP is… smalltalky? Objects are lightweight, easily defined and modified, heavy emphasis on methods-on-things, and introspection, and duck typing, and not just “hashes which can contain functions”? I see people complaining about OOP but then I realise they’re mainly complaining about Java, and sure, I hate Java too, and the way it pushes everything into Classes and Factories and whatever. I possibly might only like Ruby-style oop.
Similarly, I still can’t quite tell if, when advocating for “a more functional approach” people mean “just functions as first-class-objects I can pass around so I can be more functional if I’d like to”, like the ecmascripts… or “more top-level functions”, like Python and Lua… or if they really think everybody should be writing Haskell. Again, I like the permissiveness to do the first, which lets you write in the style of the last if you really want… but making the latter the only way just seems unfriendly to most new coders.
I feel like I’m just derailing now, but have been thinking about this thread for a bit, and so now dropping those thoughts in.
I was certainly an OOP fan when I was a young Padawan, and have done the commercial coding gigs in Java and C++. I still have a lot of respect for Java, but tend to code in it functionally: my beef with OOP is not the objects, methods, inheritance and so on, but the mutable state which makes accidental sharing and change of state an obscure source of bugs. (Actually, C++ is better in this regard because of the smartness of constructors and assignment, meaning you can build and copy stuff on the stack.) I use objects and interfaces in Clojure, but they’re stateless. Once you commit to immutable data, lots of other stuff like asynchronous communication channels makes more sense (and becomes much less risky).
By functional programming I mean the cool higher-order stuff with map, reduce, filter, partial application, function pipelining and so on. Again, these only gain traction if you can trust that your data is immutable.
Agree with all this. I also prefer to use map, filter, etc over for loops because I think it is easier to read. The named function describes exactly what it does and makes the code a bit more “self documenting.”
I think a declarative style that favors expressions that return values over an imperative style that uses statements that do things is often easier to follow. I’m probably not going to (intentionally) mutate any state in a function designed to filter a table, but if I’m working with a for loop, I might filter a table, set a boolean somewhere, and redraw the screen. Before I know it I have this massive loop that’s doing all kinds of crazy stuff and it gets harder to track down what’s going on. Of course, side effects like this are going to be necessary in a norns script, but I think it’s nice to try to isolate them both for readability and maintainability.
So, when I talk about functional programming it’s also just a preference for small, reuseable, “pure” functions that make everything a bit more modular and easier to follow (IMO).
Clojure does a pretty good job of this: it allows imperative statements (despite being a lazy language!), but only allows variable assignment in very limited circumstances, on special atoms, with transactional semantics. Most of my Clojure programs, even large ones, only have at most one “variable”.