Scala has them in a possibly even better way, where it's just special syntax over a method over strings that takes arguments. So you can make it do much more than interpolation. Haskell also has quasiquoting, which allows the same thing as scala, but the syntax is not nearly as nice. Still very powerful! And I say this as a Python fan.
My top five (modern) language constructs are: 1. RAII 2. Async (the way of taking code that would normally be event-loop style and making it very sequencial) + Generators (same concept) 3. Comptime (either Zig-style or Generics) 4. Enums and Pattern matching 5. (custom) Iterators and Iterator chaining (like saying mylist.map(it * 2).sum() I did not count a good type system here as I think every language should have this is some form or another. I think stuff like list comprehensions isn't really important because it is just syntax sugar and doesn't fundamentally change the way I code and organize my codebase.
So much of my code in python is comprehensions although i like haskells syntax better, and i would say a module system and a hindley milner type system are must haves for me
python devs need syntax sugar like list comprehensions because you can’t just chain methods/operations easily like you would in languages that are sane and dont have significant indentation
@@mikkelens they don't serve the same purpose. What does chaining have to do with list construction via human readable expressions? The concept comes from set comprehensions in mathematics and is a way of efficiently creating data structures, not to mention you can absolutely chain methods in python
@@fabianmontes7553 java ‘generics’ are literally just syntax sugar for object inheritance, and as such you can’t use primitives in generic code, only object versions of them.
you definitely missed RAII, one of the defining feature of C++ (and Rust). this feature also what makes me love both language. no more memory management headache, no more forgetting closing socket, etc.
Maybe also linear types then? It's the mathematical idea behind Rust's ownership model and it is really quite straightforward actually. As for what else (besides memory safety) you can achieve with it, I'd highly recommend reading the blog post "Introducing Austral". It contains an awesome explanation of that idea and shows a lot of great examples!
RAII is what you do when you have no clue how to handle memory. Instead of that mess, prealocate for most things and use pools/arenas. Its faster, more efficient in terms of usage, less bug prone and much less code.
@@filipg4 I think you misunderstand what RAII really means. Even pools/arenas can benefit from RAII. RAII is a very basic and versatile concept. Some languages that miss RAII and introduced a not so potent substitute like "using" in C#, the "try"-syntax-variant in Java), the "with" in Python, or the a bit more flexible "defer" in Go. In the end it is not really about not having a clue, but more about being lazy and/or forget-full, when trying to solve another problem. That what a good programming language is about, I can concentrate on the problem I want to solve, not the language mechanisms.
Yeah, I guess you've definitely missed defer construct, it's present in Odin language for example and can be implemented in C++ through destructor mechanism
drop in rust is generally a lot better than defer. It lets individual types handle their out of scope operations automatically so you can never, for example, forget to unlock a mutex
Does C# have sum types (aka rust enums, aka tagged unions) and pattern matching? And what you mean by “better execution”? Better than all presented languages?
In C++, with variants, visit and function overload you also have pattern matching, although in C++26 pattern matching will be a new core feature of the language.
I feel like a better example from Haskell would be how it unifies lists and generators. After showing how enums work in Rust you can explain how the equivalent works in Haskell and build a linked list with that. Then, since the order of evaluation in Haskell almost barely matters, if you have a pair of function where one produces a list and the other one consumes them, together they can be compiled to a simple loop if the compiler is clever enough. And in case it isn't, they will still be generating and consuming about one element at the time because the default lazy evaluation will only ask the producing function to generate the next one if the consuming function is trying to consume it. Then you could even show stuff like creating infinite lists and doing actually useful work with them!
3:02 "Instead of needing to write two functions". But you did exactly that, it's just that they are called the same and the appropriate implementation is picked automatically by the compiler based on the object it is applied to. If you need two different actions, you will have two different functions, there's absolutely no way around that. You can hide it in a bunch of layers of extra stuff, of course, but at the end of the day, it's two functions/procedures.
The pattern match is inside of the function (singular) rather than being overloading on the outside of it (which would make it two). Haskell is weird ok.
@@wareya Implementation details. As someone else mentioned in the comments, a glorified switch statement. You still write two separate pieces of code for each of the shapes, with no overlap. Inline them all you want, it's still two separate things. I'm not familiar with haskell, but I just don't think it was a good explanation / example of "pattern matching", especially if it can offer more than achieving polymorphism. I doubt "not writing two functions" is the main purpose / use case of this feature.
Yeah, this specific Haskell syntax is pretty bad at just showing pattern matching because it looks so different. A simple Switch/Case/Match-Expression would have been easier to understand because most people are already familiar with them or similar constructs. Haskell has pattern matching on function input parameters. You can think of it as a function with a Switch/Case/Match testing for each pattern like a condition. in C# and in many other languages you can use patterns inside if or variable declarations. Its basically fancy conditions that can also declare variables
@@arichidoru It's not about the work you spend writing the function, it's about what you can do with it. You can pass the function around as a single thing that handles both cases instead of having to potentially pass multiple different functions around. You can't do that with e.g. overloading.
@@arichidoru yes! You could pattern match on Lists/Arrays like [a, b, c, ...rest ] declaring a bunch of variables at once or match on specific values like [4, 2, 0, 6, 9] or do both. Languages with pattern matching let you match on anything. Classes, Types, Tuples, ... As I have said in my other comment: fancy conditions that may declare variables
Yes, I was about to eventually get to it when looking at the pattern matching example. Pattern matching is "doable" in a lot of languages that provide some level of type system, but Haskell is particularly nice because of the lack of additional things required to safely implement a pattern matching system. Speaking of Haskell, a generalization of a generator expression is "lazy evaluation" (or deferred if lazy seems derogatory). It is nice that Python has a lot of built-in generator functionality, but it can be implemented in any language. I think all of these constructs are just "sugar" (i.e. language mechanics that reduce the amount of boilerplate required), with different languages supporting different levels of pre-run safety checks.
Also check out Rust's macro feature. It's so flexible that you can literally write another language inside rust and still have compile-time checks for that language
@@Kraaven2026 yeah my favorite one is how your SQL gets compile-time checked against your structs as you code. Imagine that - you're writing an SQL statement and it's getting validated even before you write test cases for it. Absolute magic ✨
I'm interested in learning more about small-cost abstraction constructs, similar to Rust's enums, which can be evaluated at compile-time. Could you make a video of such examples? Also, you mentioned string interpolation earlier. Rust has a similar feature with its print macro. While I'm not a big fan of Rust, I find that feature quite neat.
@@nebularzz indeed everything inside std::fmt is string interpolation-like, but with limitation such as can't do operation inside a string, but that is not rust limitation, just the std correctness implementation. swift kinda have the better interpolation than others (not swift dev)
Ayo first on a gucci new video. More seriously, fun little video with an interesting topic. It's some of these things you take for granted working within one specific language, but also the fact that you will miss certain features from other languages in the one you're working in now.
Hey thanks for the feedback. I’m wondering, is it not on the screen long enough to pause on parts where you need my time to ingest it? I’m trying to find a balance :)
I'm a polyglot dev, over the years I've used a lot of different languages and have grown fond of their different abstractions and constructs. That being said, it also depends on the type of language we're talking about. You probably wouldn't want to have higher kinded types in a low-level language given how expensive it would be as an abstraction. A lot of this depends on whether or not you have a garbage collector or not. In my opinion, if you're going to point to pattern matching then you definitely can't forget elixirs variant of it. In elixir/erlang, you can do module level pattern matching. In other words, rather than having a really large function with a lot of control flow, you can just have multiple functions with the same name that match on the arguments being passed into them. Staying with elixir/erlang for a moment, actors and supervisors are also really nice language constructs. Other useful constructs include higher kinded typing, type covariance and contravariance, defer, function currying, partial application, destructuring, non-nullables, extension functions / methods, type aliasing, lazy application, eager application, opt-in immutability, first class streams/channels/pipes, futures/promises, goroutines/coroutines (green threads), result/maybe/option monads, comptime, hygienic homogeneous macros etc. I'm sure there are more constructs I could think of that I really like but these are the ones that I can think of off the top of my head.
@@kantancoding Right on, keep up the good work. By the way just curious, what animation library are you using? I really like how your videos are put together, and I also have my own programming UA-cam channel. I used to just record a live code session and then chop it down into a tutorial but it's way too time-consuming (1 hour of content would equate to about 20 minutes of video after 3-4 hours of editing) which is why I'm looking into other methods of building videos. I'm considering making slides and animations via the 3brown1blue python library or my own bespoke animation library. I figure, writing out some animations and still slides and then recording a voice over would be less time than live coding and editing things down later. Got any opinion on this? Keep up the good work.
I was introduced to parameter decomposition (as I think it was called) in Haskell. There were numerous cases for using it. It could be that a function would return two or more values, and instead of assigning them to a single constant that you'd decompose later to the useful values, you could decompose the values directly: a, b = somefunction Not only that, but you had more granularity when dealing with larger lists. You could create cases for input parameters and decompose them directly: somefunc [ first:rest ] or somefunction [first:second:rest] Finally, apologies for any Haskell pros. It's been a while since I've written Haskell, so my syntax might not be correct. Hopefully I conveyed the idea nonetheless.
I think the most useful feature is macros in Nim. Why? Because in Nim, pattern matching, generator expressions, f-strings, async and more was implemented using just macros.
I would like a part directed to Go's type embedding or Kotlin's delegate which are great tools to favor composition over inheritance. Thanks for the video!
Great video, I enjoyed watching. I personally like Python's f strings the most. Their syntax is clear and easy to remember. Although I would be open to listening to other people's takes on this
I'd say variables and consts are even more useful, and arrays or even better associative arrays :) And boolean operators are very useful too. Not sure if I really had to ever use a generator in production code at work.
7:57 that would require some compiler preprocessing to do. Besides, how does it determine in which way to stringify the value? What if i want a hexadecimal representation of an int?
Every programming language worth its salt lets you specify formatters a la C’s printf. For example the python code s = f”A plus B in hex is {a + b:x}” uses the f before a string to make it a formatted string, and takes the expression in braces and divides it up by the colon. The left half is an expression which can be evaluated (here it’s the a + b), and the right half is a format string which specifies hexadecimal (here it’s the x). By and large just about any kind of formatting can be done, like left and right alignment, different bases, so on and so forth in any language that supports this
pattern matching as you described sounds like inheritance. is there more to it than just a different syntax for instantiating abstract types with concrete implementations?
For Rust he showed you pattern matching on enums, so you can add functions to enums and provide different functionality for each enum by pattern matching. But pattern matching goes beyond this, let's say you have a character and you want to know if it's a number, you can pattern match the character to 0..=9 which means any character from 0 to 9 (inclusive because of =). It's about matching a pattern and executing code based on it. I wouldn't think about languages like Rust in terms of OOP. The Rust Book and Rust by Example Book are great free resources that you can use to learn more about this.
it has nothing to do with inheritcance. At least the Haskell one. Its just syntax feature and you can unwrap really deep into a nested data type using pattern matching in haskell. for example if you have: data NestedType = Constructr1 Maybe (Maybe Int) you can do: func : : NestedType -> Int func (Constructor1 (Just (Just 3))) = 3 * 3 pretty expressive.
Nice. I didn't know that Kotlin had F-strings by default. I can't say as though I really like the list comprehension transformations, but it looks like Julia and Python have the cleanest for loops. I think for most things, generator patterns are probably the wrong way to go because it's slower, but it depends on what you're doing. I also kind of feel like the trait composition that Rust and a few other languages use makes it more difficult to define and implement an interface. Of course, now I wish I had already learned Kotlin years ago, because who knows what other good features it has that could've inspired me in designing my own language. I decided on F-strings by default not because of being inspired by a language which did, but because Python's F-strings annoyed me.
@@kantancoding I'll have to remember to post a link when I finally publish it. I'm still working on writing the ARM code generator, so that may be a while yet.
I honestly don’t remember since I changed it since then. It’s one of the default vs code ones so if you just scroll through the dark options you should find it.
Can you share that list? I'd really like to dive more into the topics, maybe try and make a language that englobes all those features and see what comes out ¯\_(ツ)_/¯
In inheritance based polymorphism, anybody can create its own shape as long as you implement the abstract methods (it would be area in the example). With tagged union (or enum or sum types), you can't because the set is fixed. In the video example, you can't add a square after defining the shape type. Note: I am simplifying a bit here because there are other ways to do polymorphism in OOP and some languages have features to disable inheritance.
You are used to Java syntax, so something different just looks odd to you. I know both languages and I find the Haskell syntax to be much better, much more readable.
@jessedo2668 yeah you're right, in java you would create a Shape interface that defines a calculateArea method which will be implemented in the child classes, here the syntax just looks like you're casting a Shape into a Circle or a Rectangle, I know that it achieve exactly the same thing but it really seems cursed to me hahah
@@kantancoding I was about to say "it's short for enumeration!!!!!!" but then I realised how enumeration is actually pronounciations implies enum should be pronounced your way. I still think you're wrong though
I'm ok with being wrong 😉 I actually don't think either way is wrong. I've heard it both ways but I use the way that feel natural to me which is likely just a result of my accent
I would like to add implicit interfaces, it might seem like a small feature in Go for example, but the implications on the way you can design code is enormous.Your higher abstractions can define what they require, and along with dependency injection you have no coupling to lower dependencies at all, it is up to the caller to provide what is required and badabing it works.
I actually almost added how the combination of implicit interface implementation and being able to assign methods to types makes cool things possible in Go but it would have taken like 6 minutes to explain 😂 But now after reading your comment I’m regret not adding it 🫠
@@kantancoding You little bellend, I was simply being a good Samaritan to point out your mistake. You could use a one liner list comprehension in python to accomplish the same.
doesn't the inclusion of first order functions imply higher order functions and therefore composition? feels like you're listing out consequences of the first item to fatten out the list
I wasn’t trying to “fatten out” the list since the size of the list is arbitrary. I was just trying to show how some constructs can build on top of others. I did something similar with generators and list comprehension. It just felt more intuitive that way.
4:27 acting like this is a construct that is enum specific and comes from rust is criminal. This is discriminated unions and is a functional programming construct and can be found in many languages and has nothing to do with enums rust simple combined it
The construct that I am talking about is Pattern Matching. Pattern Matching in Rust uses enums. Just because in Rust, discriminated unions are typically implemented using enums, doesn't mean that I am saying that discriminated unions are enum specific or that they come from Rust. I honestly don't even see how you came to that conclusion...
Pretty sure C doesn't have first class support for functions. Compiler extensions exist that improve that, but that's compiler specific and not a part of the language itself.
what's the difference between kotlin's string interpolation and python's? I find that python's ability to customize that behaviour a lot more useful. There's a whole mini language spec for instance, f"{2 / 3:.2%}" -> 66.67%
"most useful" why is doubleAfterSquare(3) more usefull then double(square(3)) if you are desperatly looking for a selling point of your compose function maybe ..
Not that example specifically, but being able to treat functions as variables is amazing in general. I wrote a differential equation solver for a university project once. There are lots of methods to do that numerically (approximating solutions) like Euler's method, AM2, AM3, AB2, AB3, BDF-2, and more. I could then implement one step function for each method, and one solve(...) function that took one of the step functions as an argument as well as initial conditions, and then it took steps until it was done. I think I even made it a generator, so I didn't have to save the entire list of potentially 100,000s of steps if I just wanted the max y value or something. In general, it's a great tool to make template functions and separate out the specifics into their own functions.
A bunch of stuff nobody really cares about. Most of that is already included in most practical languages or not included due to their poor/lame implementationability/readability.
Higher Order Fuctions are basically functions dealing with Function-Pointers in C, you just wouldn't call them Higher-Order function in C. qsort() from the standart-library is a Higher Order Function, as it takes a pointer to a compare-functions as argument.
can you say the same between (x) => f1(f2(f3(x))) and (x) => { let y = f3(x); return f1(f2(y)); } ? composition just hides 1 level, and makes it much easier to read and less prone to errors when used correctly. i dont know how it is less debuggable, when you can easily plug in debugging function into the composition, or use debugger to step in and out, and stack is preserved with exceptions. i am not sure what self modifiable means.
This is basically a macro in C. In fact the function feof() from stdio.h is often implemented as a macro reading from the FILE structure. While macros are indeed hard to debug, they aren't really self-modifing code. In fact self-modfien code fell out of fashion - It was a MS-DOS era thing. Nowadays you have either UNIXoid high end computers that deny write acces to code, or low-end embedded systems that have the Code (as hardvard-architecture) in ROM.
The section makes all the sense and in fact has absolutely nothing to do with vtables or inheritance. Discrimminated unions are just a single interger (to flag which case it is) and the corresponding data. Since the type as a whole needs to be constant size in compiled langauges, the size is the size of the discrimminator + the size of case-data with the largest size. Any operation reading that data just checks the discrimminator to determine how to interpret the data and what actions to take. No vtables necessary.
@@relt_ Its actually much better since know you can pass the data around on the stack, instead of the heap. Vtables also don't solve the issue of wanting to pass data around that can be one of multiple different possibilities. No need to involve multiple levels of indirection and function pointers just to access data, so its much easier for a compiler to optimize too. The whole video is about broadening your horizons. Maybe try understanding how being able to simply and efficiently model a choice between a closed set differently formed data is useful for programming.
E-nooom 💖😭
it's actually e-GNOME 🧙♂️
I mean, it’s short for enumeration, so I guess I understand the thinking.
Unfortunately, I’m from California 🫠
@@kantancoding I am from earth
@@RenderingUser I’m not from Earth, I’m from Missouri
Python's "f" strings are amazing
I agree!
Scala has them in a possibly even better way, where it's just special syntax over a method over strings that takes arguments. So you can make it do much more than interpolation.
Haskell also has quasiquoting, which allows the same thing as scala, but the syntax is not nearly as nice. Still very powerful!
And I say this as a Python fan.
@@Tomyb15 Python also has a `format()` method for strings, its syntax precedes f-strings. Similar thing in C#, IIRC.
@@SugarBeetMC aren't C# $strings the same as fstrings?
Yes they are @@NihongoWakannai
My top five (modern) language constructs are:
1. RAII
2. Async (the way of taking code that would normally be event-loop style and making it very sequencial) + Generators (same concept)
3. Comptime (either Zig-style or Generics)
4. Enums and Pattern matching
5. (custom) Iterators and Iterator chaining (like saying mylist.map(it * 2).sum()
I did not count a good type system here as I think every language should have this is some form or another.
I think stuff like list comprehensions isn't really important because it is just syntax sugar and doesn't fundamentally change the way I code and organize my codebase.
So much of my code in python is comprehensions although i like haskells syntax better, and i would say a module system and a hindley milner type system are must haves for me
python devs need syntax sugar like list comprehensions because you can’t just chain methods/operations easily like you would in languages that are sane and dont have significant indentation
@@mikkelens they don't serve the same purpose. What does chaining have to do with list construction via human readable expressions? The concept comes from set comprehensions in mathematics and is a way of efficiently creating data structures, not to mention you can absolutely chain methods in python
genereics with contraints: rust, haskell, TS
function (x: T) {}
destructuring: rust, python, TS
const { x, y } = { x: 1, y: 'foo' }
error and option monads: zig, rust, haskell, gleam
Java also has generics with constraints
@@fabianmontes7553 java ‘generics’ are literally just syntax sugar for object inheritance, and as such you can’t use primitives in generic code, only object versions of them.
you definitely missed RAII, one of the defining feature of C++ (and Rust). this feature also what makes me love both language. no more memory management headache, no more forgetting closing socket, etc.
Thanks for the input. It will help for the next video :)
Maybe also linear types then? It's the mathematical idea behind Rust's ownership model and it is really quite straightforward actually.
As for what else (besides memory safety) you can achieve with it, I'd highly recommend reading the blog post "Introducing Austral". It contains an awesome explanation of that idea and shows a lot of great examples!
RAII is what you do when you have no clue how to handle memory. Instead of that mess, prealocate for most things and use pools/arenas. Its faster, more efficient in terms of usage, less bug prone and much less code.
I don't get why more languages don't use RAII
@@filipg4 I think you misunderstand what RAII really means. Even pools/arenas can benefit from RAII. RAII is a very basic and versatile concept. Some languages that miss RAII and introduced a not so potent substitute like "using" in C#, the "try"-syntax-variant in Java), the "with" in Python, or the a bit more flexible "defer" in Go. In the end it is not really about not having a clue, but more about being lazy and/or forget-full, when trying to solve another problem. That what a good programming language is about, I can concentrate on the problem I want to solve, not the language mechanisms.
Yeah, I guess you've definitely missed defer construct, it's present in Odin language for example and can be implemented in C++ through destructor mechanism
Thanks for the idea! I’ll look into it. Seems interesting 🧐😆
RAII.
Is defer in Odin like defer in Go? It's one of my favorite constructs, I wish more languages implemented it instead of having to use try/finally
drop in rust is generally a lot better than defer. It lets individual types handle their out of scope operations automatically so you can never, for example, forget to unlock a mutex
@@Ztaticify Drop does basically* the same thing destructors do. Well, 'cause the Drop trait is a destructor.
C# has all of these and arguably a better execution in many respects.
Does C# have sum types (aka rust enums, aka tagged unions) and pattern matching?
And what you mean by “better execution”? Better than all presented languages?
In C++, with variants, visit and function overload you also have pattern matching, although in C++26 pattern matching will be a new core feature of the language.
Honestly. One of the most useful and niche videos I've seen in a while. Very nice
Hey! That really means a lot. I try hard to come up with different ideas so I’m glad it’s appreciated. Thanks for watching!
I feel like a better example from Haskell would be how it unifies lists and generators. After showing how enums work in Rust you can explain how the equivalent works in Haskell and build a linked list with that.
Then, since the order of evaluation in Haskell almost barely matters, if you have a pair of function where one produces a list and the other one consumes them, together they can be compiled to a simple loop if the compiler is clever enough. And in case it isn't, they will still be generating and consuming about one element at the time because the default lazy evaluation will only ask the producing function to generate the next one if the consuming function is trying to consume it.
Then you could even show stuff like creating infinite lists and doing actually useful work with them!
Good points! Some great ideas for a future video :)
yeah he's underselling haskell here
3:02 "Instead of needing to write two functions". But you did exactly that, it's just that they are called the same and the appropriate implementation is picked automatically by the compiler based on the object it is applied to.
If you need two different actions, you will have two different functions, there's absolutely no way around that. You can hide it in a bunch of layers of extra stuff, of course, but at the end of the day, it's two functions/procedures.
The pattern match is inside of the function (singular) rather than being overloading on the outside of it (which would make it two). Haskell is weird ok.
@@wareya Implementation details. As someone else mentioned in the comments, a glorified switch statement. You still write two separate pieces of code for each of the shapes, with no overlap. Inline them all you want, it's still two separate things.
I'm not familiar with haskell, but I just don't think it was a good explanation / example of "pattern matching", especially if it can offer more than achieving polymorphism. I doubt "not writing two functions" is the main purpose / use case of this feature.
Yeah, this specific Haskell syntax is pretty bad at just showing pattern matching because it looks so different. A simple Switch/Case/Match-Expression would have been easier to understand because most people are already familiar with them or similar constructs.
Haskell has pattern matching on function input parameters.
You can think of it as a function with a Switch/Case/Match testing for each pattern like a condition.
in C# and in many other languages you can use patterns inside if or variable declarations.
Its basically fancy conditions that can also declare variables
@@arichidoru It's not about the work you spend writing the function, it's about what you can do with it. You can pass the function around as a single thing that handles both cases instead of having to potentially pass multiple different functions around. You can't do that with e.g. overloading.
@@arichidoru yes! You could pattern match on Lists/Arrays like [a, b, c, ...rest ] declaring a bunch of variables at once or match on specific values like [4, 2, 0, 6, 9] or do both.
Languages with pattern matching let you match on anything. Classes, Types, Tuples, ...
As I have said in my other comment: fancy conditions that may declare variables
It’s not really a language construct but Hindley-Milner types deserve a mention for being the best thing ever
Looks interesting! Let me look into it :)
Yes, I was about to eventually get to it when looking at the pattern matching example.
Pattern matching is "doable" in a lot of languages that provide some level of type system, but Haskell is particularly nice because of the lack of additional things required to safely implement a pattern matching system.
Speaking of Haskell, a generalization of a generator expression is "lazy evaluation" (or deferred if lazy seems derogatory). It is nice that Python has a lot of built-in generator functionality, but it can be implemented in any language.
I think all of these constructs are just "sugar" (i.e. language mechanics that reduce the amount of boilerplate required), with different languages supporting different levels of pre-run safety checks.
If a concept has a name that includes author names it's probably more math and less programming
Also check out Rust's macro feature. It's so flexible that you can literally write another language inside rust and still have compile-time checks for that language
proc macros in rust are actual dark magic
Clojure macros are more powerful because they’re unhygienic
I'll check it out!
YOO, thats cool. Do you know any examples of this?
@@Kraaven2026 yeah my favorite one is how your SQL gets compile-time checked against your structs as you code. Imagine that - you're writing an SQL statement and it's getting validated even before you write test cases for it. Absolute magic ✨
I'm interested in learning more about small-cost abstraction constructs, similar to Rust's enums, which can be evaluated at compile-time. Could you make a video of such examples? Also, you mentioned string interpolation earlier. Rust has a similar feature with its print macro. While I'm not a big fan of Rust, I find that feature quite neat.
Sure, let me look into it. Thanks for watching :)
The format macro is usually used for string interpolation and it shares (almost) the exact same syntax as the print macros
@@nebularzz indeed everything inside std::fmt is string interpolation-like, but with limitation such as can't do operation inside a string, but that is not rust limitation, just the std correctness implementation. swift kinda have the better interpolation than others (not swift dev)
Funny that C# has all those concepts, but it hasn't been mentioned once.
Sorry about that brother. I’ll give C# more spotlight in a future video.
Ayo first on a gucci new video.
More seriously, fun little video with an interesting topic. It's some of these things you take for granted working within one specific language, but also the fact that you will miss certain features from other languages in the one you're working in now.
I agree! Especially if you are currently working with a language that you don’t particularly like 😂
@@kantancoding surely that'd never happen 🫠
Id appreciate it if the code stayed on the screen longer, instead of modifying it in real time - makes it easier to follow, in my opinion
Hey thanks for the feedback. I’m wondering, is it not on the screen long enough to pause on parts where you need my time to ingest it? I’m trying to find a balance :)
I am making my own language, so you know I am taking notes! 😎
interesting
Same and completely did that for the Haskell bits. I've much to learn.
@@Vivraan I don't think anyone fully understands Haskell. Lol.
Is that list available somewhere? Would love to check if there's something I miss or didn't know the term for.
What font and theme are you using?
please make more videos like this 👍
Will do!
@@kantancoding what too do you use to make these clean animations.
what font are you using?
I'm a polyglot dev, over the years I've used a lot of different languages and have grown fond of their different abstractions and constructs. That being said, it also depends on the type of language we're talking about. You probably wouldn't want to have higher kinded types in a low-level language given how expensive it would be as an abstraction. A lot of this depends on whether or not you have a garbage collector or not.
In my opinion, if you're going to point to pattern matching then you definitely can't forget elixirs variant of it. In elixir/erlang, you can do module level pattern matching. In other words, rather than having a really large function with a lot of control flow, you can just have multiple functions with the same name that match on the arguments being passed into them. Staying with elixir/erlang for a moment, actors and supervisors are also really nice language constructs.
Other useful constructs include higher kinded typing, type covariance and contravariance, defer, function currying, partial application, destructuring, non-nullables, extension functions / methods, type aliasing, lazy application, eager application, opt-in immutability, first class streams/channels/pipes, futures/promises, goroutines/coroutines (green threads), result/maybe/option monads, comptime, hygienic homogeneous macros etc. I'm sure there are more constructs I could think of that I really like but these are the ones that I can think of off the top of my head.
Great input! Some of these actually almost made it into this video. I will definitely consider them if I make another :)
@@kantancoding Right on, keep up the good work. By the way just curious, what animation library are you using? I really like how your videos are put together, and I also have my own programming UA-cam channel. I used to just record a live code session and then chop it down into a tutorial but it's way too time-consuming (1 hour of content would equate to about 20 minutes of video after 3-4 hours of editing) which is why I'm looking into other methods of building videos. I'm considering making slides and animations via the 3brown1blue python library or my own bespoke animation library. I figure, writing out some animations and still slides and then recording a voice over would be less time than live coding and editing things down later. Got any opinion on this? Keep up the good work.
Have you tried manim?
@@kantancoding I figured that's what you're using. Okay yeah I'll give it a shot.
I was introduced to parameter decomposition (as I think it was called) in Haskell.
There were numerous cases for using it. It could be that a function would return two or more values, and instead of assigning them to a single constant that you'd decompose later to the useful values, you could decompose the values directly:
a, b = somefunction
Not only that, but you had more granularity when dealing with larger lists. You could create cases for input parameters and decompose them directly:
somefunc [ first:rest ]
or
somefunction [first:second:rest]
Finally, apologies for any Haskell pros. It's been a while since I've written Haskell, so my syntax might not be correct. Hopefully I conveyed the idea nonetheless.
Nice! Interesting stuff. Thanks for sharing 😊
I think the most useful feature is macros in Nim. Why? Because in Nim, pattern matching, generator expressions, f-strings, async and more was implemented using just macros.
I would like a part directed to Go's type embedding or Kotlin's delegate which are great tools to favor composition over inheritance. Thanks for the video!
Great point! Thanks for the input. There are a couple more from Go that I wanted to add. Just might make another video.
@@kantancoding Thank you !
Amazing editing and explanation
do you have a link on the list of construct in the video in 0:10 seconds
Interesting concepts include SkookumScript's (and now UEFN's Verse's) first-class parallel execution primitives.
The game dev bros always hit me with the wild stuff. Gotta check it out
Operator overloading, which almost any OO language supports.
Thank you :)
Great video, I enjoyed watching. I personally like Python's f strings the most. Their syntax is clear and easy to remember. Although I would be open to listening to other people's takes on this
I actually like Python’s f strings as well. Probably my second favorite. Thanks for watching btw!
Nice video. 't would have been nice to see elm during function composition
Great suggestion! Maybe in the next one :)
I'd say variables and consts are even more useful, and arrays or even better associative arrays :) And boolean operators are very useful too. Not sure if I really had to ever use a generator in production code at work.
Of course you are correct but I opted to leave out the obvious language constructs for obvious reasons 😊
@@kantancoding you even forgot about recursion ;)
lol did I?
7:57 that would require some compiler preprocessing to do. Besides, how does it determine in which way to stringify the value? What if i want a hexadecimal representation of an int?
Every programming language worth its salt lets you specify formatters a la C’s printf.
For example the python code
s = f”A plus B in hex is {a + b:x}”
uses the f before a string to make it a formatted string, and takes the expression in braces and divides it up by the colon. The left half is an expression which can be evaluated (here it’s the a + b), and the right half is a format string which specifies hexadecimal (here it’s the x).
By and large just about any kind of formatting can be done, like left and right alignment, different bases, so on and so forth in any language that supports this
What a nice fucking video. I love the vibe in this video, and the concept too. Looking forward to more videos
Thank you! More videos coming soon :)
Sick graphics ❤
Thanks mate! And thanks for watching 😊
Missed Clojure macros and erlang actor model. It’s expected.
Thanks for the input. It will help for the next video :)
@@kantancoding If you do a video on any of these topics, then I better subscribe to check it out!
pattern matching as you described sounds like inheritance. is there more to it than just a different syntax for instantiating abstract types with concrete implementations?
For Rust he showed you pattern matching on enums, so you can add functions to enums and provide different functionality for each enum by pattern matching.
But pattern matching goes beyond this, let's say you have a character and you want to know if it's a number, you can pattern match the character to 0..=9 which means any character from 0 to 9 (inclusive because of =). It's about matching a pattern and executing code based on it. I wouldn't think about languages like Rust in terms of OOP.
The Rust Book and Rust by Example Book are great free resources that you can use to learn more about this.
it has nothing to do with inheritcance. At least the Haskell one. Its just syntax feature and you can unwrap really deep into a nested data type using pattern matching in haskell.
for example if you have:
data NestedType = Constructr1 Maybe (Maybe Int)
you can do:
func : : NestedType -> Int
func (Constructor1 (Just (Just 3))) = 3 * 3
pretty expressive.
Nice. I didn't know that Kotlin had F-strings by default. I can't say as though I really like the list comprehension transformations, but it looks like Julia and Python have the cleanest for loops. I think for most things, generator patterns are probably the wrong way to go because it's slower, but it depends on what you're doing. I also kind of feel like the trait composition that Rust and a few other languages use makes it more difficult to define and implement an interface. Of course, now I wish I had already learned Kotlin years ago, because who knows what other good features it has that could've inspired me in designing my own language. I decided on F-strings by default not because of being inspired by a language which did, but because Python's F-strings annoyed me.
Good points. Would be interested in knowing more about your language.
@@kantancoding I'll have to remember to post a link when I finally publish it. I'm still working on writing the ARM code generator, so that may be a while yet.
Python example should use generator/yield pattern, this is faster and more generic than list.append.
why do you say enum like it's the name of a goofy side character in Star Wars
It’s because enum is short for enumerable which is pronounced that way. I still prefer “ee•numb” personally though.
I know it has nothing to do with the video, but, which theme did you use for the Python codes?
I honestly don’t remember since I changed it since then. It’s one of the default vs code ones so if you just scroll through the dark options you should find it.
@@kantancoding Oh, thanks! I'll check then
macros and metaprogramming are cool too :)
Can you share that list? I'd really like to dive more into the topics, maybe try and make a language that englobes all those features and see what comes out ¯\_(ツ)_/¯
I want list comprehensions in Go D:
My favorite feature of Go is probably the easy cross compilation :-)
Generatoe Expressions are RAD!!!!! (i never used them tho)
Correct me if I'm wrong but the way I understood it, the pattern matching in Haskell is "genius-ingly" _"just"_ polymorphism outside of OOP, right ?
no, it's like a beefed up switch
@@yjlom the difference between this beefed up switch and polymorphism being ?
In inheritance based polymorphism, anybody can create its own shape as long as you implement the abstract methods (it would be area in the example). With tagged union (or enum or sum types), you can't because the set is fixed. In the video example, you can't add a square after defining the shape type.
Note: I am simplifying a bit here because there are other ways to do polymorphism in OOP and some languages have features to disable inheritance.
@@thefanboy3285
valid Haskell, invalid Java:
and :: Bool -> Bool -> Bool
True `and` True = True
_ `and` _ = False
boolean and(bool true, bool true){return true;}
boolean and(bool default, bool default){return false;}
invalid Haskell, valid Java:
talkTo :: Cat -> String
talkTo c = "Meow"
talkTo :: Dog -> String
talkTo d = "Woof"
String talkTo(Cat c){return "Meow";}
String talkTo(Dog d){return "Woof";}
@@yjlom i have no idea what you just wrote
And scala has all of them :3
Yeah, Scala is great :)
2:44 as a java dev, this is cursed. Like, soooo freaking cursed, I see what is achieved with this but the syntax is just cursed and I hate it
You are used to Java syntax, so something different just looks odd to you. I know both languages and I find the Haskell syntax to be much better, much more readable.
@jessedo2668 yeah you're right, in java you would create a Shape interface that defines a calculateArea method which will be implemented in the child classes, here the syntax just looks like you're casting a Shape into a Circle or a Rectangle, I know that it achieve exactly the same thing but it really seems cursed to me hahah
Yeah good points. Polymorphism is a bit different imo but I didn’t go deep enough with the example to really show why.
enUm
Tomato tomato 🇺🇸
@@kantancoding I was about to say "it's short for enumeration!!!!!!" but then I realised how enumeration is actually pronounciations implies enum should be pronounced your way. I still think you're wrong though
I'm ok with being wrong 😉 I actually don't think either way is wrong. I've heard it both ways but I use the way that feel natural to me which is likely just a result of my accent
What font is this
In the description
@@kantancodingi didn't see any font mentioned in the description
@@kantancoding there’s no font in the description
Nice idea for a video.
Thanks! I’m glad you think so :)
I would like to add implicit interfaces, it might seem like a small feature in Go for example, but the implications on the way you can design code is enormous.Your higher abstractions can define what they require, and along with dependency injection you have no coupling to lower dependencies at all, it is up to the caller to provide what is required and badabing it works.
I actually almost added how the combination of implicit interface implementation and being able to assign methods to types makes cool things possible in Go but it would have taken like 6 minutes to explain 😂
But now after reading your comment I’m regret not adding it 🫠
@@kantancoding Understandable! Nice video nonetheless mate.
Honestly, iterator is imo easier to read and write than list comprehension
+1.
In rust we can do just do
(0..100).filter(|x| x**2>3).map(|x| x*2)
much easier to read and understand imo
no way
Haskell does it all
In your python code example in the thumbnail, you can do it in one line, it's called List Comprehension.
Bro, stop trolling me 😂
@@kantancoding You little bellend, I was simply being a good Samaritan to point out your mistake. You could use a one liner list comprehension in python to accomplish the same.
😂 is that so?
@@kantancoding Yup. S = [2*x for x in range(100) if x**2 > 3]. Does exactly the same thing.
Oh you’re right! I had no idea. Thanks bud.
What font is this? So i can avoid it
😂
“Sir you can’t smoke that here!”
“Function”
“Seriously, stop!”
“Function(() => double_it_n_give_to_next_person())
😂😂😂
Kotlin GOAT
doesn't the inclusion of first order functions imply higher order functions and therefore composition?
feels like you're listing out consequences of the first item to fatten out the list
I wasn’t trying to “fatten out” the list since the size of the list is arbitrary. I was just trying to show how some constructs can build on top of others. I did something similar with generators and list comprehension. It just felt more intuitive that way.
Destructuring
Good video!
Thanks! Glad you liked it.
In all fairness, enumberable isn't pronounced e-numb-berable
2:10 First feature C doesn't support :)
Much respect for my C bros out there
It does with function pointers
@@elpower5226 Everything until that point. But not pattern matching. Because that's mostly a syntax feature.
Someone on 4chan made an edit that made you look like you did a really bad crime in Japan
🤣
Most useful construct in C is not even in C, it's __attribute__((cleanup())).
List 0:11
I haven't understood sh*t, god I hate being a beginner.
You'll get there brother! Join the discord if you need support!
4:27 acting like this is a construct that is enum specific and comes from rust is criminal. This is discriminated unions and is a functional programming construct and can be found in many languages and has nothing to do with enums rust simple combined it
He didn't, though, he covered Haskell first.
The construct that I am talking about is Pattern Matching. Pattern Matching in Rust uses enums. Just because in Rust, discriminated unions are typically implemented using enums, doesn't mean that I am saying that discriminated unions are enum specific or that they come from Rust. I honestly don't even see how you came to that conclusion...
Show me an actually used language that doesn't have some concept of passing functions around as values!
Pretty sure C doesn't have first class support for functions. Compiler extensions exist that improve that, but that's compiler specific and not a part of the language itself.
@@nsshurtzThere are function pointers, but they are annoying to use
Yeah imo function pointers don’t count. I’ll definitely pass 😂
nice bideo
Spelling so bad youtube is offering to translate this to English
what's the difference between kotlin's string interpolation and python's? I find that python's ability to customize that behaviour a lot more useful. There's a whole mini language spec for instance, f"{2 / 3:.2%}" -> 66.67%
I think they're pretty similar actually. I too am a fan of Python's f strings :)
Lua proxy tables
Nice!
C# ❤❤❤
Eenoom....eenoom
Enoom
0:08 - looks like that list is just an HTML-like file. Why would you compile it? ;-)
We humans could interpret it on the fly!
Woah woah… I wouldn’t touch HTML.
C#, which has all of these, is purposely ignored 👌
C# will have its day
"most useful"
why is doubleAfterSquare(3) more usefull then double(square(3))
if you are desperatly looking for a selling point of your compose function maybe ..
Not that example specifically, but being able to treat functions as variables is amazing in general.
I wrote a differential equation solver for a university project once. There are lots of methods to do that numerically (approximating solutions) like Euler's method, AM2, AM3, AB2, AB3, BDF-2, and more. I could then implement one step function for each method, and one solve(...) function that took one of the step functions as an argument as well as initial conditions, and then it took steps until it was done. I think I even made it a generator, so I didn't have to save the entire list of potentially 100,000s of steps if I just wanted the max y value or something.
In general, it's a great tool to make template functions and separate out the specifics into their own functions.
apart from python's list comprehensions, everything else in the video is present in almost all functional programming languages.
c# linq
Yes sir
C# is best ❤🎉
Certainly is pretty good now. Hopefully it finishes stealing everything good from F# and becomes perfect
4:57 That looks unreadable and crammed
Absolutely. It's terrible compared to just iterator methods.
List comprehension is a crime against god and mand.
A bunch of stuff nobody really cares about. Most of that is already included in most practical languages or not included due to their poor/lame implementationability/readability.
Embrace the status quo! Nothing good will ever happen in the future
Higher Order Fuctions are basically functions dealing with Function-Pointers in C, you just wouldn't call them Higher-Order function in C.
qsort() from the standart-library is a Higher Order Function, as it takes a pointer to a compare-functions as argument.
1:39 This is nothing useful and also dangerous because self modifiable code is undebuggable and prone to errors
can you say the same between (x) => f1(f2(f3(x)))
and (x) => { let y = f3(x); return f1(f2(y)); } ?
composition just hides 1 level, and makes it much easier to read and less prone to errors when used correctly. i dont know how it is less debuggable, when you can easily plug in debugging function into the composition, or use debugger to step in and out, and stack is preserved with exceptions. i am not sure what self modifiable means.
This is basically a macro in C. In fact the function feof() from stdio.h is often implemented as a macro reading from the FILE structure.
While macros are indeed hard to debug, they aren't really self-modifing code.
In fact self-modfien code fell out of fashion - It was a MS-DOS era thing. Nowadays you have either UNIXoid high end computers that deny write acces to code, or low-end embedded systems that have the Code (as hardvard-architecture) in ROM.
2:16 this whole section makes zero sense. I think you meant to talk about vtables instead of "combining constructors"
The section makes all the sense and in fact has absolutely nothing to do with vtables or inheritance.
Discrimminated unions are just a single interger (to flag which case it is) and the corresponding data. Since the type as a whole needs to be constant size in compiled langauges, the size is the size of the discrimminator + the size of case-data with the largest size.
Any operation reading that data just checks the discrimminator to determine how to interpret the data and what actions to take.
No vtables necessary.
@@mkwpaul that's even worse than vtables!
@@relt_ Its actually much better since know you can pass the data around on the stack, instead of the heap.
Vtables also don't solve the issue of wanting to pass data around that can be one of multiple different possibilities. No need to involve multiple levels of indirection and function pointers just to access data, so its much easier for a compiler to optimize too.
The whole video is about broadening your horizons. Maybe try understanding how being able to simply and efficiently model a choice between a closed set differently formed data is useful for programming.
@@relt_ no it performs better than vtables.