28 ms·
Dynamic types have the potential to be more than "no static types"
- zozbot234 4y agoWhat does this have to do with dynamic typing specifically? A language "where everything is a runtime construct" can only be a language where there's no phase separation between compile time and run time. So it makes just as much sense to say that everything is a compile time construct.
- mannykannot 4y agoNot all programs have a compile time (and for those that do, there's a one-to-many relationship between it and runtimes), but there's always a distinction between runtime and write-time. I do not see how one could write a program without having in mind specific semantics for each identifier you introduce, and if they have specific semantics, are they not at least implicitly typed, whether or not they are statically typed? The author seems to want to use dynamic typing in a restricted manner, to help in testing and debugging and understanding a program, particularly within a REPL. Further down, he seems to be saying that the lack of empirical evidence on how this works in practice (on account of it rarely, if ever, being used in available tooling) seems to suggest it is difficult.
- zozbot234 4y agoWhy do you assume that one can't assign a semantics to code that happens to be included within the program as a runtime construct? Such semantics might even be "compile-time" checked in a conventional way, if one can come up with a machine-checked proof that the runtime treatment of such "constructs" does preserve whatever semantics one cares about. Of course this runs into concerns with Turing-completeness in the fully general case, but for many practical scenarios such a check might well be feasible.
- mannykannot 4y agoI'm not saying you cannot do that (and, specifically, I am not saying you cannot do the sort of things the author seems to want to do.) I am saying that I don't see how you can write the program in the first place, unless you have some semantics in mind for every identifier you introduce. To add to that, I doubt to can perform a runtime substitution unless the substitution conforms to the semantics of the original design. This seems to be the case with the author's examples, which extend the original types with a sidechannel for getting information about the execution of the program.
- mey 4y agoMy personal opinion has become, do I want to write leaky tests to validate typing (in addition to functional tests) or do I want to let my compiler do my type validation for me in exchange for the boiler plate on the front end with my IDE providing auto complete along the way? As a Java programmer, you can guess where I lean. Now excuse me while this autowired annotation Dependency Injection causes a new self inflicted level of hell.
- goto11 4y agoYou shouldn't write test to validate types in the fist place. Testing for the correct behavior should be enough. I like static typing, but his this is just a silly argument. If the program behaves correctly according to spec, how can the types be wrong?
- Yoric 4y agoI may be misunderstanding what you write, but I've had many (many, many) programs that worked for the wrong reasons, which led them to break in very (very, very) ugly ways due to unrelated changes. The only ways I know of to get rid of (some of) these bugs are to: - use as much static typing as possible; - wherever static typing is not sufficient, sprinkle this with a paranoid dose of assertions. If I read what you write correctly, that's way beyond "testing for the correct behavior", no?
- goto11 4y ago> which led them to break in very (very, very) ugly ways due to unrelated changes. Sounds more like a problem with tight coupling than a problem with types per se. A function should not change behavior due to an "unrelated change", regardless of static or dynamic typing.
- Yoric 4y agoLet me give you a trivial example (with TypeScript-style syntax, but assuming no TypeScript-style type-checking): ``` function foo(config: {hasYakShaver: boolean}) { if (config.hasYakShaver) { // ... } else { // ... } } foo({hasChuckYeager: false}); ``` This will accidentally do what I want, but for bad reasons. And one day, quite possibly, a change that appears unrelated will break this. And that's me being nice. If I start using reflection to do complicated stuff, or if I'm using C++ and the reason for which my code works is memory corruption... well, when I need to figure out what suddenly broke the code, I'm going to be up for serious a moment of loneliness.
- otikik 4y agoLisp seems alive and well.
- mountainriver 4y ago100% agree with this. I recently had a problem in python where dynamically generating the classes was the cleanest implementation. What I found was basically no IDEs supported runtime generation of code for things like autocomplete. Jupyter does but it’s a limiting environment. It made me realize this is a mostly unexplored space with little tooling
- quonn 4y agoI think F# could do this well.
- Koshkin 4y ago.NET can do it: https://learn.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/dynamic-source-code-generation-and-compilation https://learn.microsoft.com/en-us/dotnet/framework/reflectio...
- bob1029 4y agoSee also - Source Generators, which can replace some forms of reflection. https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/s...
- int_19h 4y agoA big reason why this kind of stuff doesn't quickly become unreadable in C# is that the output of a source generator must be a new file. So the ability to extend existing code is limited to the facilities that the language specifically provides for this purpose (partial classes & members, extension methods etc). It also means that tooling can treat all such generated code as virtual files, allowing them to be inspected, debugged etc.
- quonn 4y agoI meant this which is different: https://learn.microsoft.com/en-us/dotnet/fsharp/tutorials/type-providers/ https://learn.microsoft.com/en-us/dotnet/fsharp/tutorials/ty... For example, there is an SQL provider that gives you generated types and auto-complete but it does this without running a source generator that emits files. It just happens inside the compiler.
- Akronymus 4y agoDynamic typing can be quize useful, but sadly languages like python, IMO, lack the supporting infra, compared to, lets say, LISP.
- talideon 4y agoSuch as? My first guess is that you're referring to macros, but I'm also guessing that you might be referring to its lack of homoiconicity.
- Akronymus 4y agoYeah, homoiciniciry is one of the "facilities" I mentioned. Along with stuff like pattern matching over types. But yeah, macros are really one of rhe big ones.
- andybak 4y agoI'm not going to pick on anyone in particular but I came to the comments with a sense of forboding that people saw the words "static" and "dynamic" and wrote stuff unrelated to TFA. I was not disappointed. This article is complex and interesting and deserves better.
- jerf 4y agoFragility of the more clever dynamic typing stuff like the "Replacer" is a big problem. An even bigger problem is the increasing difficulty of composing those things together when you want more than one at a time. And that difficulty scales up fast. Consider the Python function decorator. It was quickly discovered that the decorators need to preserve the metadata for the function's name, etc., or the decorated functions broke in places that looked at that metadata. But if you have a "function name" parameter that the decorators can modify, now there's a mismatch between the fact that the function claims to be "the original GetUser function" but it's actually the decorated function. So now you ought to have "the visible function name" and "the real function name", so for instance, the former works with metaprogramming correctly but the latter allows for correct debugging output on crashes. But what if you decorate a decorator and then want to do metaprogramming on it? Now you're got the apparent function name, the real name of the function beneath it, and the real name of the function beneath that. And this is just one dimension of problem. There are many others when you get to metaprogramming like this. The core problem is really this style of metaprogramming, in my opinion. It almost inevitably slips into becoming Deception-Based Programming, in which increasing swathes of the program are trying to figure out what lies to tell to other chunks of the program to get the desired effect. Dynamic typing makes this harder because it makes it very difficult to get a manifest that lists all the behaviors this particular chunk of code or code object has, but even if you were doing this in a static language that didn't make that hard, a language would still have a problem exposing all the correct properties and assisting programmers in getting them completely correct. It's fun at first, and very powerful at first. But deception-based programming is one of the quintessential ways a code base fails to scale and becomes an incomprehensible tangled mass, code bases that use heuristics to figure out what lies to tell to other bits of the programming to cover up the effects of the lies the other bits of the code are telling. This leads to your programming becoming an embodiment of the phrase "What a tangled web we weave, when first we practice to deceive". That said, before someone pops up with "but we could do that without deception"... I agree! Or at least, I agree that it should be explored better. I would be interested in a language that somehow enables such metaprogramming, but with a philosophy that says that it will stay "honest" throughout, and basically deliberately eschew and exclude this sort of "transparency". I don't think it's necessarily intrinsic to the style. I just think that it's sooooo tempting that if you don't build a language that excludes it from the get go that the temptation to just shim this little behavior in just here is too tempting, plus the languages tend to end up requiring it, be it accidentally or deliberately, because they afford that behavior and the language ends up being built around how it is at least supposed to be possible. I don't know entirely what this would look like. Edit: I say I don't know what this would look like, but I have had a couple of ideas. One I've talked about before is to have a clean separation between an "initialization" phase in which metaprogramming is legal, and a "sealing" phase in which it is all done and the metaprogramming becomes invalid/illegal. I wrote this for the runtime not to have to be super flexible all the time, but it works for programmers using the system to know that things won't change out from under them. The other is some convenient and powerful way to dump things out in their final state post-manipulation. I've gotten some good mileage out of this in my own code that is using a lot of composition. It is very helpful to be able to take the top level of the composed object and say "Tell me what exactly you are made out of". Even if I'm not doing metaprogramming based on the response, it's a huge debugging help. I see traces and fragments of this idea here and there, but no system that has it coherently integrated in a way I'd like to see.
- coldpie 4y agoI think we've largely settled on "doing clever things is bad, actually." At least, I have. Your code should be obvious and self-documenting. Relying on language features to do a bunch of stuff behind the scenes more often leads to programs that are hard to debug and perform badly. How many times have you had to dig into C++ operator overloading compiler errors or someone being too clever with C macros (wait, that macro can return?!?) or someone overloading a Python built-in function in surprising ways. Coming across the short double example from the article in production code would make me groan. The "all sorts of uses for this kind of trick" the author lists sound like a nightmare for some poor employee to have to reverse-engineer five years from now. I feel modern languages (Rust, Go, Swift) have moved away from clever, implicit behavior in favor of more considered and explicit code-efficiency mechanisms. For example making error handling (? error operator in Swift & Rust) an explicit first-class citizen, rather than some clever macro hacking; or the trend away from operator overloading and complicated class structures and towards explicitly declared interfaces; or the many restrictions on Rust macros[1] to help ensure they're maintainable. [1] https://veykril.github.io/tlborm/decl-macros/minutiae/hygiene.html https://veykril.github.io/tlborm/decl-macros/minutiae/hygien... and related sections
- zozbot234 4y ago'?' in Rust isn't quite a first-class citizen. It would be one if Rust supported general effects.
- __jem 4y agoIn what way is '?' not first-class error handling just because it does not exist in the context of a generalized effects framework? I don't see at all why handling other effects is a requirement for error-handling to have good ergonomics in the language.
- fizx 4y agoAs a sometimes-clever programmer, I find it interesting that Github Copilot is discouraging my inherent cleverness.
- int_19h 4y ago
- dwohnitmok 4y agoMy suspicion is that what makes this hard is using higher-order dynamic programming facilities. If you just stick to first-order functions with relatively simple data structures and primitive types then there's a lot of cool, interesting runtime gadgets you can put in. But the moment you try to use the full arsenal of dynamic reflection, metaprogramming, etc. then the runtime checks and gadgetry needed to analyze all of that explodes in complexity and even if it's possible to make an analysis framework capable of analyzing it, the results might be very difficult to interpret. So as long as you try to stick to first-order constructs as much as possible, you can get quite far.
- int_19h 4y agoBut, conversely, the simple cases that you describe can be handled quite nicely without losing static typing. You just need union types, and some simple type inference. So then why bother with dynamic at all?
- skydhash 4y agoAFAIK, there is always a separation between the program (represented by the code) and the process (the execution of the program by the machine). To “hyperprogram”, we would need to merge the two. I view the program as a space-based entity and the process as a time-based entity. How do we map time to space? Especially if it's a non-deterministic process? Lisp macros allow us to specify multiple versions of code at compile time. And Lisp conditions allow us to redefine or discard runtime contexts. But this time-based manipulation is simpler than what you seemingly want. The easiest way I can think of is to offer an API to track the evolution of types, but I'm guessing the performance hit would be heavy.
- pavlov 4y agoSmalltalk traditionally breaks this source vs. executable state dichotomy by using a single memory image where everything is an editable object, including the classes that contain the code. For persistence, the image is simply stored and reloaded in its entirety.
- UglyToad 4y agoThis doesn't seem specific or particularly enabled by dynamic types? I don't really understand it yet and have barely used it but this seems like it's supported by expression trees / Roslyn / code gen stuff in .NET/C#? Is there a difference I'm missing here, e.g. writing some lambda and having the code generate code to run it against NoSQL or SQL or some other storage at runtime which is what I think things like LINQ2SQL do under the hood is basically equivalent right? This type of programming seems like it can be supported by both types of types?
- munificent 4y agoRuntime metaprogramming has been the raison dêtre of Ruby since Rails first took off. And, yes, you can do really cool incredibly powerful stuff with it. Likewise any Lisp with their various macro systems. CLOS in Common Lisp can also do very powerful cross-cutting metaprogramming stuff. The key problem has been that the resulting programs are fantastically hard to understand. One of the things I think Dijkstra got right in "Goto Considered Harmful" is that humans can reason about programs better when there's a strong correspondence between the textual structure of the code and its execution structure at runtime. Runtime metaprogramming deliberately upends that. I think it's probably totally fine to use those kinds of features as debugging aids when hacking on a program and figuring out where stuff is going wrong. That aligns with many of the use cases Hillel lists here. But you probably don't want to commit code like that to the repo and require everyone else maintaining the program to understand it. For better or worse, we seem to lean strongly towards languages and language features that are used not just during the development process but also make sense in committed, maintained code. Dynamically typed languages lean in the other direction. They can be very powerful and productive while you're in the middle of working on a program, but the resulting artifact is harder for others to understand and maintain. Dynamically typed languages to me feel like unbaked plasticine. You can have a lot of fun molding it and you can get it into the shape you want much quicker than you could carving wood or stone. But if a dozen people are collaborating on a sculpture over months or years, it's impossible to build on top of someone else's plasticine part without it shushing down and falling apart. Or course, the holy grail is baked plasticine: a language that is dynamically typed and moldable while you're figuring stuff out and then can be baked with static types for the parts that are solid and need to be reliable. I've yet to see an optionally or gradually typed language pull that off well. The boundary between them just gets too weird.
- jfengel 4y agoIn the 80s there was an April Fools issue of "Communications of the ACM" positing a "COME FROM" statement (not too long after "GOTO Considered Harmful"): https://dl.acm.org/doi/10.1145/358027.358043 https://dl.acm.org/doi/10.1145/358027.358043 It was a joke because of course it was obviously worse than GOTO. But much of modern programming seems to consist precisely of COME FROM style constructs. A common term is "inversion of control", which can also mean "You don't have control". As with metaprogramming, it's powerful in that it can take control of your system without your direction, but dangerous in that it's hard to predict when it will. I find that the hardest thing to debug is when things don't happen, when it's out of your control to make them happen. There's no place to put a debugger on "never happened". Dynamic types, similarly, are hard to debug because they fail by not happening. Errors aren't caught, but there's no instant at which it wasn't caught because it was "any time".
- lisper 4y ago> They [dynamic types] have potential to be more than "no static types", but that hasn't happened. It happened thirty years ago: https://en.wikipedia.org/wiki/The_Art_of_the_Metaobject_Protocol https://en.wikipedia.org/wiki/The_Art_of_the_Metaobject_Prot... But the thing that really drives me nuts about this never-ending debate is that static and dynamic are not a dichotomy. You don't have to choose one or the other. You can have both. The real choices are: 1. Do you want the language to require the coder to provide type information (e.g. Java), or permit the coder to provide type information (e.g. Common Lisp) or forbid the coder from providing type information (e.g. early versions of Python, Javascript). 2. How much work do you want the compiler to do with regards to finding potential problems at compile-time, and what do you want it to do when it finds one? Options include: do nothing (e.g. Javascript), warn if it finds a problem (e.g. C), or forbid you to run the program if it finds one (Java, Haskell). Personally these choices have always seemed like no-brainers to me: allow but don't require type information, and warn when the compiler finds a problem but allow the program to run anyway. What possible advantage is there to be gained from any other choice?
- iaabtpbtpnn 4y agoWhy do you want to run programs that are wrong?
- aserafini 4y agoBecause the property of being wrong is not binary. It might be partly wrong but correct enough for your current situation.
- majewsky 4y agoHow do you know when it's crossed into being just a little too wrong?
- ReflectedImage 4y agoYou can have a catch all exception handler that emails any exceptions that occur to you. It's not pretty but it can be done. :P
- MichaelBurge 4y agoI feel like Common Lisp should be considered in this article. I think it can implement the specific points mentioned, and (quote x) introduces Gödelian difficulties for type systems that even the Haskells of the world don't handle well. It's in some sense the "most dynamic" language, because you start one instance and repeatedly mutate it. And people mainly use introspection as the main tool for finding definitions/help/etc. instead of static documentation, so you don't have a half-hearted REPL. I'm more of a static types person myself, but CL would be what I compare them to.
- prewett 4y agoWait till this guy discovers Lisp, self-modifying code, or eval()! Code and data are exactly the same thing, just stuff in memory! The problem is that infinite malleability is cool, but a lot of tasks do not benefit from it. Static typing, on the other hand, keeps you from making a lot of dumb mistakes. Static typing lets you spend time on your logic, whereas with dynamic typed languages like JS or Python, you spend time on logic AND on typos, misremembering function arguments and or positions, etc. The thing is, most routines can only really accept one or two types, and only very rarely does one want to treat data as code. A word processor, or a sorting algorithm, or banking library have no need to modify their code; in fact, doing so is likely to break something. The sorting algorithm would benefit from polymorphism, but the others would not. You cannot render an image the same way as text. And the banking library had better be adding integers, not strings or even floats. However, all programs will benefit from the compiler saying "the function doesn't accept this type" or "this variable doesn't actually exist". This guy should find a job maintaining a complex Python server, he'll learn why people like static types. Nothing like digging through a bunch of code to find, "ah this looks like the function I need, what does it take? Oh 'connection'. What's that?" Static typing at least forces a minimum of documentation. Or you make a change, server runs but still not fixed. Then you notice it's not even doing what it used to do, but no errors or anything. After tearing your hair out, you discover you swapped two letters in a variable, which caused a syntax error exception loading the module, which was silently discarded by the dynamic loading code.
- pavlov 4y ago> “This guy should find a job maintaining a complex Python server, he'll learn why people like static types.” This guy wrote several books on formal methods. Not only does he know why people like static types, he works to encourage people to adopt more advanced forms of static analysis.
- spankalee 4y agoThis post seems to conflate runtime metaprogramming (and maybe operator overloading and proxies too) with dynamic typing. You can have runtime metaprogramming, reflection, and reified types with static type systems.
- feoren 4y agoThis is one of the things C# does extremely well. Expressions are an under-rated (and sadly under-maintained, e.g. no tuple support) feature of C# and one of its big differentiators versus other languages for me. I use reflection heavily as well.
- pipeline_peak 4y agoI recommend learning static typed languages first, maybe even working in a large code base. Then you will truly appreciate the ambiguity of dynamic languages and look at them less like “baby languages”. In app code bases, there are always multiple duplicates of the same type, no one truly respects reusability partially because it’s not always practical. With dynamic typing, you can be completely transparent and have multiple “Car” classes. It doesn’t matter because types don’t matter. To us they both represent a car, no need for converters, helpers etc. Car1 definition has some method you need for Car2? No problem, just tack it on in that scope! It’s not polluting the definition of Car2, it's not going anywhere.
- taeric 4y agoShame that "dynamic types" is often conflated with just non-static. My favorite "dynamic" type that is stupid useful has to be numbers in Common Lisp. (I think most all lisps, actually?) Having it widen to be as big of a number as you need it to be, while you are using it, is stupid useful.
- LAC-Tech 4y agoDoes CL have something akin to schemes "numeric tower"? number -> complex -> real -> rational -> integer. Always idly thought that could work in a statically typed lang and was a feature well worth copying (how many languages REPLs are useless as calculators due to floating point errors!)
- taeric 4y agoThe general idea should be fine. The problem gets in that I have to make a choice up front in many static languages. Something I don't have to do if I give up what exact type of number I am using at the moment.
- masklinn 4y agoThat has nothing to do with dynamic types? Haskell has infinite-precision numbers.
- LudwigNagasena 4y agoHonestly, I always felt a bit lost in the whole “static vs dynamic types” debate because I dislike how it’s framed. So “a language is statically typed if you know its type at compile-time”. Ok, so you have ParentClass and ChildClassA and ChildClassB that inherit from it. And you create a variable that holds objects of the type ParentClass. But do you know at compile-time whether it is ChildClassA or ChildClassB? Only if your type inference system is strong enough and your code is structured appropriately for such possibility to even exist. And do you even need to know it? Most likely not unless that one time when you do in which case you use some ugly reflection tricks and whatnot. And, bam, now your “statically typed” language actually has dynamic type checking. And if you use a “statically typed” language, but have an algebraic type that holds int or string or ClassA or Array, how useful is that static typing? That’s why I prefer calling the code either weekly typed or strongly typed. And some languages allow you to have strongly typed code with ease, and some languages allow you to have weakly typed code with ease, and some, like TypeScript, allow you both in which case the whole dilemma seems unwarranted or at least outdated.
- masklinn 4y ago> Ok, so you have ParentClass and ChildClassA and ChildClassB that inherit from it. Why do you even assume classes are involved? > And, bam, now your “statically typed” language actually has dynamic type checking. Not in any way, shape, or form? You have a ParentClass, it lets you perform ParentClass operations. If you want to do a ChildClassA operation if that's that, you check whether that's what you have, then do the operation. Both are enforced by the compiler (though how safe the check is varies). If you have a dynamically typed language, you might think you have a ParentClass but you actually have a completely unrelated ToyotaSupra. > And if you use a “statically typed” language, but have an algebraic type that holds int or string or ClassA or Array, how useful is that static typing? Extremely. Because you know what the potential is, and you have to check for it exhaustively. > That’s why I prefer calling the code either weekly typed or strongly typed. So complete nonsense to make you feel good?
- pwdisswordfish9 4y ago>> And, bam, now your “statically typed” language actually has dynamic type checking. > Not in any way, shape, or form? https://en.cppreference.com/w/cpp/language/type#Dynamic_type https://en.cppreference.com/w/cpp/language/type#Dynamic_type https://en.cppreference.com/w/cpp/language/dynamic_cast https://en.cppreference.com/w/cpp/language/dynamic_cast https://en.cppreference.com/w/cpp/language/typeid https://en.cppreference.com/w/cpp/language/typeid It’s there in the name.
- LAC-Tech 4y ago"smug static weenies" LOL. I really, really don't mind if people have preference for static typing. I do too - not as strong as the 2022 zeitgeist to be sure but I still have it. What I dislike is the inference that people who prefer dynamic typing are ignorant, that they just haven't discovered hindley-milner and need to be converted. A lot of incredibly smart, talented programmers chose (and choose) to do work with dynamically typed languages. Don't assume it was because they knew less than you.
- claytongulick 4y agoThanks for this. After ~30 years of professional software development in pretty much every major language created, I prefer dynamic languages to statically types ones for a whole host of reasons, none of which amount to me being a n00b or uneducated, which is frequently implied by the static types die-hard folks.
- tines 4y agoLanguage researcher here, would you be willing to list out some of those reasons you mentioned? Always interested in hearing these perspectives!
- diffxx 4y agoNot the OP, but most compilers for languages with static type systems are too slow for my taste. I have a workflow for writing lua programs in which I embed all of my tests in the same file that contains all of the runtime logic. Whenever that file changes, I have a monitor script that runs the program and validates my tests. This process typically happens in less than 20ms, so I've configured my editor to save my lua files whenever I type a single keystroke. It's like having a living REPL session that is persistently stored in the file. When you develop in this style, you tend not to write type errors because you are constantly validating your program inputs and outputs, which the tight feedback loop encourages. Most statically typed languages have slow compilers. It is not uncommon for an incremental compile to take at least 1s, which is 50-100x slower than what I'm used to. Indeed, I find it distracting when compilation is that slow so with such a language I will still use a monitor script for iteration but I will manually save when I want validation. This means that I am not only getting slower feedback, I am getting less of it, which ultimately makes me less confident in my program. If I were making lots of type errors, maybe it would be worth it to have a type checker, but I'm generally not.
- masklinn 4y ago> I don’t know if any of these are impossible without runtime metaprogramming, but they all seem like they’d be more difficult. Not really? I mean it is more difficult in that you have to specify all sort of things which are left unsaid in the article's snippet, but fundamentally you just need the ability to define or override the act of "calling" an object, aka void operator () () or impl FnOnce<...> for <...> Something not all statically typed languages have, but not all dynamically typed languages either (doesn't exist in javascript for instance, you can staple shit on a function object or extend Function but AFAIK the language has no callability protocol). > I can imagine all sorts of uses for this kind of trick. Here’s some I’d like to see: Most (though not all) of these are already better and more generally solved via introspection tooling. > I guess the closest to the hyperprogramming model is Pharo? The dynamic code inspector is a big deal, plus there’s this fun talk. The author should take a look at Self. It has similarities with Smalltalk systems, but is an other window into an... interesting world.
- jesse__ 4y agoFirst off, interesting blog. I can appreciate the author thinks that dynamic types are cool, and he/she/they like being weird and contrarian. That's cool. Me too. That said, I'd like to point out that the article focuses on dynamic runtime-metaprogramming, but (largely) fails to address it's statically-typed cousin, compile-time metaprogramming. I'd like to broaden the conversation a bit. I'll quote the author: > a dynamically typed language is one where types are runtime values and manipulable like all other values. It’s a short hop from there to thinking of the whole runtime environment in the same way, where everything is a runtime construct. If you have a decent compile-time metaprogramming system paired with a statically typed/compiled language, (almost) the same statement holds true. It's not as easy to, for example, instrument a production instance with brand new code at runtime, but the mental model while programming is very similar. > The overall picture is of going one step further than homoiconicity: instead of code as data we have programs as data. This is the heart of what metaprogramming is all about and, to my mind, really has nothing to do with static vs dynamic types. The author finishes with 'crushing disappointment'. I somewhat share this sentiment, though I have great hope as well; there are some incredibly talented developers working on the compile-time version of these problems. Maybe one day we'll have good languages. I guess the only thing we can do is continue writing new ones until we do ;)
- karmakaze 4y agoCame to say roughly the same thing. The post seems to conflate a number of things. Static/dynamic is about compile time (in dev environment) and run time in (production). Not being opposite doesn't mean that you need much of both depending on how expressive the compile time is. Types being runtime values can also work during the compile runtime as noted. A very good example to look at is the `comptime` in Zig, it could be enlightening and the opposite of 'crushing disappointment'.
- layer8 4y agoIn the end, static typing is just a means for the reasoning you do in your head about why your program is correct to be checkable by the compiler. Arguably, if you’re unable to explain that reasoning to a compiler-like checker, then you shouldn’t trust your reasoning. Of course, static type systems aren’t quite there yet in practice, but that’s the ideal. And runtime metaprogramming is no different. You have some reasoning in your head why your metaprogramming code is doing the correct thing, and it should be possible to have a type/theorem checker that validates your reasoning. So static typing vs. dynamic typing isn’t about what you can or cannot do, it’s about having a mechanism to verify the soundness of your reasoning vs. having no such mechanism.
- ReflectedImage 4y agoUnless you are using Haskell or Rust then static typing is a terrible way to check if your program is correct. You need unit tests to confirm the correctness of your program. And unit tests check most of the types for free. So you don't need static typing after all.
- kaashif 4y agoThe person you're replying to didn't mention this, but I find the main utility of static typing to be that all variables and functions either have explicit types or their types can be inferred by the IDE. Great for readability when I'm wondering what type this parameter is supposed to be and what I can do with it. You can obviously have documentation of types without static types, but static typing means you have ever-present compiler-verified documentation of types. Not always enough, but it's a lot more helpful than just having nothing. Having exhaustive unit tests doesn't necessarily help with this.
- ReflectedImage 4y agoMost types can be inferred via your IDE, you generally get what you are talking about anyway for free.
- sysread 4y agoSoooooooo... what he's looking for and failing to find is Perl, Type::Tiny, and Moose :P use Moose; sub foo { ... } around foo => sub { say "Called with args: @_"; }
- ilc 4y agoThe thing most programmers forget: We don't spend most of our time writing generic libraries. We don't write generic code, nor do we want to. We write code that takes specific inputs, and generates specific outputs. So I want to use statically typed languages, maybe with a template system at MOST. If it makes my life more difficult when I write a library, I accept that. Source: LISP programmer for 6 years. Learned the above the hard way.
- rowanG077 4y ago> I encounter a lot more smug static weenies than smug dynamic weenies, so I defend dynamic typing out of spite. There have been a few cases where I was surrounded by SDWs and I immediately flipped around to being pro-static. I find such statements incredibly daunting. Like the person is acting like a child for no other reason then to be contrarian. I'd hate to work with such a person.
- munificent 4y agoThe persona someone adopts for a blog where the goal is to spark discussion and learn is probably different from the persona they adopt at work where the goal is to work with others and get things done. We contain multitudes.
- AnthonBerg 4y agoYou want something called higher-order unification. For it to work you need totality checking. What you want is dependent types. Dependent types are sensible, navigable, comprehensible, ah-what-I-wrote-last-week-is-understandable metaprogramming.
- stuaxo 4y agoNot pytest, but for ordinary python tests I have often got them running in Jupyter for prototyping purposes, it's pretty useful.