HN user

ddragon

597 karma
Posts0
Comments213
View on HN
No posts found.

I fail to see how timing and luck isn't a factor. It's more than how popular it was when it launched, many of those languages that are allegedly better had a strong timing disadvantage by either not existing or not being mature once the data science boom occurred (including equivalents to libraries like numpy, scipy, matplotlib and theano), allowing python to be the right option then. Any language that missed the timing must now play catch up with a fraction of the resources and completely unproven in the market.

Luck is harder to quantify, but at the very least competitors like common lisp didn't have much of it.

That would be simplifying too much. There are a lot of external factors for something being popular, like timing, luck, support from big enterprises and leading colleges, inertia and sunk costs. You could argue that does make python better regardless of the language itself but that poster was talking about a hypothetical scenario in which those factors were won by a language better designed for those tasks. Would you use python if most libraries, docs and support were elsewhere just for the language design as is today?

Julia 1.9 3 years ago

I'm quite interested in the interactive thread pool (although I assume it works based on conventions of everyone playing nice). Julia seems to have a powerful parallelism model but it couldn't apply it to responsive GUI and web frameworks that requires low latency, so it is nice if you indeed can have for example the tasks handling HTTP request focusing on handling it as fast as possible while the background working threads dealing with larger computations use all the speed of the Julia language without being constantly interrupted.

That issue actually doesn't happen with regular elixir, since it's immutable and stateless, the scheduler doesn't wait for the process to voluntarily wield (it's always safe to switch, so it just gives some fixed time for each process, which makes it very low latency and very reliable but not very efficient at any individual task due to all the switching). Calling other languages from erlang/elixir create dirty processes that can't be scheduled this way and may cause those issues.

Since I didn't use dirty processes in elixir it did make me forget about this obvious issue you pointed out, that for a mutable language like Julia can happen in every thread, but that's not something that limits the expressiveness of the model, but something that requires consideration to avoid while programming and language level mechanisms to protect the thread (at the very least the ability to define timeouts that can throw an exception on any spawned process) or maybe a future framework on top of it that handles this in a safer way (something like Akka). I can only hope Julia can achieve the full potential of it's multithreading model.

I didn't go deep on Julia's multithreading, but what he is saying is that Julia uses an MxN threading (I think nowadays if you don't specify it at startup it will just use one for each cpu thread), which is the same as a language I did most of distributed programming (elixir/erlang), and as far as I know it's the same as Go.

Having 1 kernel thread for each CPU thread means that your program can use all available CPU threads at the same time (so you get all the parallelism available within the machine), and having a language based scheduler for each thread means you can have minimal overhead (no need to do a system call) to create a new concurrent execution (meaning lightweight/green threading similar to what python allows, except being automatically distributed by the language within all kernel/cpu threads). In Elixir this means you can create millions of processes even though the OS will only see one thread per logical cpu thread, and I never felt the limitation of this abstraction over multiprocessing (of course, Julia is definitely nowhere near as mature - and maybe never will due to stuff like preemptive scheduling and parallel garbage collection that is easier to implement in a language with only immutable types, though it seems to be moving along, and in Julia 1.7 the processes being able to move between kernel threads solving the issue mentioned in that discussion you linked).

6DOF gyro

A gyroscope is used to detect orientation/angular velocity (spinning), the sensor to add the other degrees of freedom is already there in most modern controllers and smartphones (the accelerometer). The issue is still accuracy I'm afraid.

Do you need to rotate the yaw of your hand? Nope.

I'd certainly enjoy to open doors and make a simple goodbye gesture in VR.

I mean, I had a vita and the gyroscope control was more accurate than the stick for shooters but that's because I'll naturally adjust if it overshoots (if I go to above I'll immediately push slightly down in a feedback loop - so here what really matters is the precision, not accuracy and in fact I can even adjust the sensitivity to my preference). That feedback loop with the user doesn't work well in VR, if my hand overshoots I don't have means of resetting the position (I can only compensate, but it's extremely uncomfortable when you feel your hand in position x, look at it and it's at position y and that x-y mapping will keep changing over time - and of course it's even worse with your head PoV not matching your head movements). Of course there are lots of issues as well, how do you get the perfect initial position? After all gyroscope/accelerometers only measure movement, it can't know where it starts (for example for jogging you need a gps to get a measurement of position, just like you need a camera/laser sensor for current VR). For gyroscope in traditional gaming you usually use the stick to adjust a solid start position, which is not possible in VR as well unless you force the user to stay in a perfect pose at the start of every level after inputting arms length and height as an example, which would definitely be annoying quickly if you need to reset frequently).

And finally, you example (splatoon 2) only needs to compute 2 degrees of freedom in movement (rotation left-right - or yawing, rotation down-up - or pitching, since rolling isn't relevant with a dot target), while VR systems depend on 6 degrees of freedom (yawing, pitching, rolling, elevating, strafing and surging - all of these for at least 3 devices at the same time: your head, left hand and right hand). Unfortunately controls in VR are quite complicated, and accelerometers, gyroscopes (and magnetometers which are also used in VR systems to know the reference to the floor) are simply insufficient (but necessary since the positional sensors can't keep track all time with occasional occlusion, such as having one hand passing over the other or leaving the tracking area), which is why the same sensors on the switch are used in every VR headset and controls in addition with even more sensors and algorithms.

EDIT: the camera system also helps a lot with defining gaming boundaries in the room and being able to quickly see if I accidentally leave it, I already punched my monitor once and that's with a barrier that always get visible when I approach something in my room.

In my experience with a rift s, even though the oculus touch also has gyroscopes and accelerometers, they only help for a few seconds at most when the controllers leave the camera. Those sensors are just not accurate enough (I know little about the details of the sensors, but accelerometers are tracking the second derivative of the position, so any small error will accumulate fast when you want the latter), and you don't want to have your hand all over the place when you're trying to interact with things in VR, which is why, at least for now, you need to measure position directly for it to work, such as the camera/LED devices that are most popular with VR headsets and controllers (and even stuff like the PS Move controller).

I don't think there is anyone developing packages on pluto/jupyter, so I wouldn't worry about that. The most common method for that should be using an editor like VSCode (which will have some linting capabilities) with an open REPL and Revise [1]. What it does is every time you save any of your files (with some known restrictions), it will automatically and incrementally update the state of your application in the REPL, allowing you to probe your code whenever you want (with tons of introspection methods, up to interactively inspecting the native code being generated), and since you never leave the session you don't face the compiling latency more than once. I end up preferring this workflow for experimenting and data science stuff since it retains the structure and tooling of an editor with the ability to interact with my application (and really miss it when I'm writing Python applications), but of course each one has a preferred workflow and it'd be nice if Julia supported more of them as well.

[1] https://github.com/timholy/Revise.jl

While that might happen (and probably cause a method redefinition), there is an important convention that helps preventing it: your package must either own the function or at least one of the types used for arguments, otherwise you're practicing type piracy [1]. I've seen an automated scripts that can detect type piracy, so hopefully it could be part of a linting toolset eventually since not everyone might be aware, but at the very least popular packages shouldn't have them - or at least not in a way that may cause bugs (and if any package has it unintentionally it's probably worth creating an issue).

[1] https://docs.julialang.org/en/v1/manual/style-guide/#Avoid-t...

Languages with multiple dispatch aren't rare, but a language having it as the core language paradigm, combined with a compiler capable of completely resolving the method calls during compile time, and therefore able to remove all runtime costs of the dispatch, and a community that fully embraced the idea of creating composable ecosystems is something unique to Julia. I don't think anyone has scaled multiple dispatch to the level of Julia's ecosystem before.

Returning a Union of int or float isn't that useful but the point is that Julia is a dynamic language, and if there was no implicit union type it would have to just box the return type into an "Any" box, which actually slows down the program (the union here will cause functions to have two optimized versions, one for int and one for float instead of a generic dynamic one for Any). If it had a compile error for every function that can return more than one type it would make the language even more restrictive than some static languages.

Though for intentional uses of unions, most of the time I use a union of a success type or an error type, or a union of a type and null, or a union of type and missing. They could all be special cases, but I don't see the point of not being just one mechanism.

Objects aren't bag-of-functions though (they have state, inheritance, initializers/destructors, interface/abstract classes, classes vs objects and tons of other concepts and patterns) and any complex program can become a large hierarchic tree of classes and graph of objects that goes way beyond a simple bag-of-function. Even modules that are almost literally bag-of-functions will scale quickly to something more complex.

The point is that simple concepts are nice to explain for a beginner, but what actually built your intuition in how to use objects is the years and years learning and experiencing it's benefits and pitfalls. With multiple dispatch it's the same, but since few languages use it (and even fewer, if any, pushes it everywhere like Julia does) most people didn't experience this process.

For me when I'm using a function I just consider them as self-contained abstractions over the arguments. For example there are hundreds of implementations of sum (+), which in practice I ignore and only think about the concept of addition no matter what arguments I give and I trust the compiler/library to find the optimal implementation of the concept or fail (meaning I have to write one myself). If I'm writing a method (or function) I consider arguments as whatever acts the way I need so that I can implement the the concept on them (for example if I'm writing a tensor sum I just consider arguments as n-dimensional iterable arrays and implement assuming that - and declare for the compiler when my method is applicable, without having to care about all other implementations of sum - if anyone needs a scalar sum them that person can implement it and through collaboration we all expand the concept of sum).

And the fact that whoever uses a function can abstract away the implementation, and whoever writes a function can abstract away the whole extension of the arguments (through both duck typing and the fact that the compiler will deal with choosing the correct implementation of the concept) means everything plays along fine without having to deal with details of each side.

I do wonder if one of the factors is simply the matter of the modern earbuds and headphones becoming so convenient that you never really need to take them out.

I used to listen to songs with a wired conventional headphone for when I wanted to isolate myself from distractions for high attention work, but range limitations like the cable connected to the PC didn't allow me to move freely, no transparency mode so I had to take them out to talk with anyone, no multipoint bluetooth so I had to take them out to answer my phone and not having cables and being light in general just means I even forget I have them on. Now with a sony 1000xm4 basically all those opportunities to have my ears rest became unnecessary.

Add to that the fact that the ANC just opens up the possibility of listening to less fatiguing and attention drawing songs, so while I may listen to lower volumes I might also listen for longer periods and with less interruptions. Not to forget services like spotify with the possibility of listening to an eternally long playlist without any thought (while with a CD or mp3 playlist it would eventually reach an end from which I'd have to stop and think if I want to continue listening).

In this case it's not just a library like the wrappers for tensorflow already present in many languages (including python), it's something like a Swift language fork in which the language constructs are analyzed by the forked compiler during compilation in order to directly generate an equivalent tensorflow execution graph (without manually creating a tape through library calls). Thus it's a Swift (compiler plugin) for tensorflow.

The intention was to merge the fork into the main language making Swift differentiation tools first class within the language.

If they weren't already-made in Julia yes (and a library didn't do the work of composing it and presenting as a monolith, since pretty much all large frameworks like Flux and DiffEq are also made this way). Although you're maybe trying to imply that everything that anyone needs is already made in python, I occasionally end up finding small issues between my use case and what's immediately available and I have to twist my problem into a stitch of "kinda what I need" pieces (like having to change my problem into a vectorization problem, a long chain of corrections using pandas to fit an interface and then some weird logic to transform the table into a CSV in memory to use postgres' COPY or UPSERT because pandas to_sql is not flexible enough and iterating and inserting is too slow). Sure I always achieve the result using already-made stuff, but the glue logic ends up taking more time and honestly ends up quite hard to maintain for other people (or even myself after a few months) after all it isn't my algorithm logic but some adhoc puzzle I just solved.

In Julia, if the DataFrame library is missing something I can just loop like an array and have a method that works just as well as if the library provided it, the CSV, DB and table processing libraries all use the same conventions so if library "A" solves my issue I'm not forced to use the same library "A" serialization method. Basically instead of twisting the logic to what I'm given, I just fill in the blanks. Sure Julia has way more blanks, but Julia also has half of the age of some of the Python's library I use, it's more about the maturity of the community than anything to do with the design choices of the language and I can only hope it gets better and better with time.

That seems the start of something pretty amazing, I work on a fintech using Elixir and we usually have to delegate all numerical analysis and ML stuff to python microservices, even models with considerably simple execution, and being able to move that logic to Elixir would certainly be very nice (we can always batch train them elsewhere, just running the model is a great deal). Of course, it's definitely going to be a long road before getting to be production ready and somewhat competitive with the ever improving big players (as it is with all languages without supports from major corporations with large interest in the area, languages like Nim, Julia, Clojure and even Swift considering it doesn't have full support on Google, and those languages still have an advantage with their stronger interop with python/java/C).

But after listening to the talk, I went from thinking that Elixir was not a good fit to ML to actually excited with the future. Elixir's pipes are definitely a beautiful and concise way to define transformations, and we already have nice examples of them with Streams and Ecto.Multi that even make it surprisingly nice to debug the readable "tape", and being immutable itself allows it to nicely avoid the hardest parts in translating the language into an execution graph both for the creators of the library and for the user. Modifying the if macro to embed the conditional within the "tape" might be a little too much magic though.

That example in Julia would look the exact same since it's a very simple example with just one object/struct and only one version of each function (so it doesn't even need function overloading, let alone multiple dispatch), which is also why it might not be a good example against Python OOP, even though I'm inclined to agree, since it doesn't even need single dispatch (which if needed can be achieved by functools.singledispatch in Python).

The difference would start when you get later a different entity like for example an Organization. Now you have to either create a save_items_org (and check which one it is to choose the correct method) or you add the same check to save_items(Union[Client,Organization]...) and all other methods. If you had used the object then it would be client.save_items() or org.save_item(). Using functools.singledispatch or Julia, save_items(org...) and save_items(client...) will work like the object version and dispatch without trouble and keep as concise as the object version.

Now, the difference with Julia, is that Python's native dispatch capability ends here. If you now want a method to link Users with Users, Users with Organizations and Organizations with Organizations (sorry for the weird usecase, I don't want to add more types) you can just implement in Julia a link(Users, Organization), link (Organization, Organization), link(User, User) and any combination and whatever combination of User and Organization you receive it will dispatch to the correct version (while in Python you'll be forced to do the runtime check with an if to choose the implementation). For example the product operator (*) in Julia has around 300 implementations for all kinds of combinations of arguments, and the compiler makes it extremely fast by inferring and doing static dispatch.

Elixir is basically Erlang, just like Lisp Flavored Erlang and Gleam (the statically-checked language for the BEAM). The BEAM in general is a very specialized VM, which is why I used as an example, as the Julia compiler is also a very specialized platform and other languages targeting it would also be just Julia. Like Julia with s-expression, a typescript like extension for Julia that maybe would focus on different tradeoffs by reducing polymorphism in exchange for type checked contracts (but still use the same machinery and ecosystem since it would compile to Julia/Julia IR). I really wouldn't expect something completely different like the JVM supports (as at this point it would be better to use the LLVM directly).

Yes, I was thinking about someone going many step furthers, like Elixir is for Erlang and the BEAM, someone who disagrees with design decisions of the Julia language but appreciates the compiler's capabilities and the ecosystem enough to use them to implement their own vision for a programming language. Making it a Lisp is just one of the more obvious scenarios, as someone may like Julia in general but want the full power of s-expressions.

While not nearly as mature as the main platforms (like the JVM, .NET, BEAM), the Julia compiler does have some unique features (an extremely aggressive optimizer, a sophisticated type inference mechanism, possibly the most exceptional multiple dispatch support, Go-like lightweight processes), including a macro system that supports reader macros for writing parsers (like this library) and extensible multi-stage compilation (like the IR manipulation that is used in auto-differentiation of source code), which makes me wonder if once all those features (and possibly new ones) become robust enough it will become one of the targets for people creating new programming languages.

While you do interface with it all the time, it's not something you'll be thinking all the time, at some point it will be completely invisible even though it's there (unless you're that bored). What will really be a big part of the experience is what offers actual resistance to solving your problem and actively waste brain power and not muscle memory, be it the fact that language semantics forces you to write the same thing all the time when a particular feature (like macros) could handle it trivially, the lack of some particular type safety makes it so you keep losing time debugging the same error, the interactive tools being lacking forcing you to waste time debugging with prints, you have a workflow where the JIT lag keeps breaking your pace, the community doesn't have a culture of documentation making it so you'll lose time trying to decode the source, etc...

In general, if you code like Python (highly dynamic code with no consideration to performance) it will be closer to Python in speed, and if you code like C/Fortran (completely static, overspecified types) it should be closer to C in performance, the variance in performance in terms of naive implementations is pretty high. That means it's easy to get into Julia and start programming no matter your background, but idiomatic Julia (which it's not something you'll learn in a day) should be concise and high level like Python (and frequently more concise) and close to C in speed.

For example, what other dynamic languages do like verbosely typing everything doesn't really work in Julia (the compiler already knows pretty much every type even without hints), what works is treating the variable as a polymorphic container instead of a dynamic container: you don't know yet what type the variable has (only the behaviour), but whatever it is you should avoid changing it if possible (what they call type stability). Which is kinda why it might not be obvious reading proper high performance Julia code, as it is not something you do to make it fast, but what you don't do (change a variable type, forcing the compiler to create a low performance dynamic box, plus other stuff like global variables).

"Ever" could be a very long time, and lots of things can happen.

For example it might be possible to AoT compile parts of the code (as long as the types are fully defined) to export as a standalone library, and if that happens it would be beneficial to write the number crunching libraries for glue languages in Julia. I mean, there are already those (like DiffEq to Python and R), but if it's a binary that could have just as easily come from C it would be even better. And there are already works along this path, including for wasm deployment.

The multithreading in Julia is very fast and easy to use, and I'm sure it will become robust and safer as well as the language gets older, which can very well allow for an Akka style framework that allow for large software with both high reliability (thanks to stuff like supervision trees) and structured code (since like Julia's ecosystem design favors small libraries composing with each other, the future of large Julia codebases could become the composition of many small high level but fast actors).

There is also lots of interests in work-arounds on the garbage collector, like improved support to immutable structures (like 1.5 improvements on structs with references to mutable structures). It's possible that you might be able to more reliably write parts of the code that do not allocate (or allocate in predictable ways), so you'll be even handle those realtime code loops.

Sure this is very speculative, but look at Python today compared to when it had Julia's age. It took 8 years since Julia became public to get to this point (2 years since it got the first stable release, 1 year since it got it's multithreading model), it will surely be different when it has triple that age.

The for loop example was just to illustrate the difference between function and macro, a complete DSL would be something like making prolog within a Lisp or other examples in racket [1], or for example a special configuration file for electric circuits within the language, or a SQL like interface for manipulating data (you can write LINQ using macros).

[1] https://en.wikipedia.org/wiki/Racket_features#Language_Exten...

It's not a direct comparison of course (nor it is a Lisp), but here is an example of linear optimization in a library that uses macros to make it a DSL closer to the description (in Julia @ before a name means it's macro, so it's easy to see) and one that uses methods:

https://nbviewer.jupyter.org/github/jump-dev/JuMPTutorials.j...

https://www.cvxpy.org/examples/basic/linear_program.html

If in the Julia example they if @variable was a function, then x >= 0 would have been evaluated immediately and it would fail since x was not defined (and if it was x >= 0 would return a boolean). To emulate that you'd probably have to pass a string "x >= 0", which the function would then have to parse (it would be a DSL as well, but one you're writing from scratch), the difference here is that you can just use the language parser directly and compile already with the result.

Lisp macros (as opposed to macros in other languages besides C) are especially powerful not because you're changing the syntax, as Lisp doesn't have a syntax in the first place, you're always working directly on it's Abstract Syntax Tree. So stuff like conditional, loops and functions all have the same structure, which is why you could already write something that looks like a loop but it's a function.

The difference is how it's arguments are evaluated, for example if I wanted to make a function that implements a for loop of the form (myfor a from 1 to 10 do (print a)), if it's a function then every argument will be evaluated immediately, so it will try to find a variable called a, from, to, do and it will also try to evaluate (print a) immediately before looping. Macros allows for delayed evaluation, as myfor will receive all arguments (at compile-time) as symbols instead of values, which you can then rearrange in a form that can evaluate properly at runtime. You can also go further if you actually want to create syntax and use reader macros, which allows you to write your own parser and therefore escape writing directly on the AST (then you can even write a C syntax within Lisp).

And if your question is: do I actually need them? The answer is obviously no as many languages do not support it (and there are even alternatives for many use cases, like lazy evaluation). The advantage is that your language can have a very simple core and features that were not implemented (say a pattern match structure) can be added entirely within userspace (which is also good for testing new functionality before adding to the core language). Macros are also very efficient since they run at compilation (so if you use the macro a lot of time you only have to evaluate them once, unlike functions that will usually have to run it's logic every time it's called). And all of that means that for DSL, it's not just making a nice adaptation for your domain within the host language, but effectively writing an optimized language for your domain reusing the compiler of the host language without having to change it's source code.

I have doubts that the powerful tools that Lisp provides don't work in large teams. OOP was declared the default abstraction for managing software in large teams but there were still a lot of unmanageable code, but still people kept using it and pooling their experiences, creating innumerous design patterns to handle each limitation, and through "natural" selection the do and don't of OOP became more and more clear.

Lisp on the other side not only had less of that collective experience due to becoming less popular, but it was also famous as the language that gives super powers allowing programmers to do 10x more, so the experience was also biased for single dev performance. The Lisp Curse is a cultural problem, not a technological one, you don't need to reinvent stuff just because it is easy (and fun).

I'm optimistic though since the new generation of languages (closure, elixir, julia, nim, rust) is increasingly going against the rooted belief in OOP (stuff like inheritance) and incorporating more Lisp features like macros, code as data and everything as expression. This means more and more large dev groups will have access to the tools and reason to make it scalable in order to get a little of that super power under control.

The power of Lisp but without parentheses so Lispers can have something to complain.

But seriously, for me is something similar to Python vs Ruby (I used to prefer programming in Ruby before Python became the default ML language and made so I pretty much had to change). Like Ruby, Julia is a language that I can adapt to the problem instead of having to adapt the problem to the language. In Python there is only one obvious way of doing it (at the higher level) no matter what the problem is, usually some hard to read chain of pandas transformation followed by not much better numpy/jax or pytorch method. In Julia I feel less restricted, and if a problem is clean using loops and arrays I do with loops and arrays, if it's clean with vectorization I do that with broadcasting, if someone enabled some macro that looks almost like a description of the problem I can use that as well.

And the result ends up not being what people fear that having many ways to do something means the programmer will abuse it and write unreadable code golf, but having a more natural approach to each problem makes it easier for me to read after it's done, it's not language A twisted in language B (like those pandas operations), but something that is close to a direct representation of language A that I don't need to convert mentally or even with a dictionary.

While I like do...end for symmetry, most of the time the end will have the same indentation as whatever opened it, and everything in between will be one indentation ahead (especially if you're using an automatic formatter), so I can follow easily without reading the content. And whatever opened it will also explicitly tell the context (if it's a method definition, if/else, for...).

In the end my code ends up like python but with an extra end, which, while adding a possibly unnecessary line of code it at least makes it trivial to copy and paste something into the repl (or other part of the code) without worrying about indentation.