6 ms·
All you need is data and functions
- lincpa 4y ago[dead]
- agentultra 4y agoIn some ways, in Haskell, type-classes are passing around "dictionaries". You can implement a similar pattern as in Gleam, in Haskell, by explicitly passing around those dictionaries. In Haskell it makes for some pretty ugly code though. But type-classes get a dual use also as constraints for parametric polymorphism. This gives you some more immediate feedback from the type checker and also lets the programmer extend type checking with their own rules. Never the less I sometimes use this pattern of grouping functions together into a record along with pointers and handles they need to shared resources to make running those functions, passing them around, and cleaning them up easy and explicit -- when it is called for. From experience I find I doubt that we tend towards complexity or enjoy writing complex code. Most people I know find complex code takes quite a lot of effort to write, understand, test, and make useful. The majority of people I've worked with want to write simple, elegant code and find joy in understanding a problem. It happens that some times we either judge the problem to be more simple than it is or we can't formulate a succinct specification of the solution. This leads to code needing some solution to the inherent complexity in the former case. And in the latter it means we don't know what we're doing and are getting lost in the weeds. In both cases a liberal application of good-old-fashioned thinking tends to do the trick. When thinking is insufficient it always helps to have coding guidelines, mentors, and access to experienced colleagues. The last ditch effort of course is searching the good, old Internet.
- Joker_vD 4y ago> Instead of a trait, just make a type that implements the generic behavior you want, and then write a function to convert your data-type into your trait-type Well, yeah, obviously, when you have a single implementation of a trait/interface, then you can do away with this additional indirection, sure. But what if I have vastly different types implementing the same trait and I want to collect them all in a single list? > If you need some data-type specific logic, then pass around functions as necessary (usually from your conversion function). That... basically amounts to manually bundling values of the concrete type together with the implementation of trait and then passing them around together (also manually), isn't it? I mean, on one hand I get the message: you don't really need any fancy type systems, having integers and bytes and arrays of integers and arrays of bytes for types, ability to declare constants, global and local variables, and functions (not closures) is quite enough already to implement anything. Structs then can be emulated either as short integer arrays of fixed lengths, or with SoA approach: all enums are just integers, and pointers are also but an integer index in some globally known array so yeah, there is nothing much else you need to store in a struct's field. But let me tell you, programming this way is not very pleasant if you've spent majority of your life writing Python or Haskell. Having common patterns of data and code organization baked straight into the language is nice.
- lenkite 4y ago> That... basically amounts to manually bundling values of the concrete type together with the implementation of trait and then passing them around together (also manually), isn't it? That is more or less similar to implementing the Go interface pointer or Rust "dyn" fat pointer manually. And of-course everyone will do this their own way instead of the language providing the standard. I make a bet that next major version of Gleam will have traits once its designers are enlightened on the need for the same.
- Joker_vD 4y agoBut can you even do that in Gleam? It's statically-typed after all, so what would the types look like? I guess there is gleam/dynamic with its Dynamic type and from() and decodeN() for coercions but...
- proto_lambda 4y agoIt looks like the author is confused about or unaware of the difference between static and dynamic dispatch. Their Display and Iterator types are really just vtables; the correct comparison to Rust would be the `dyn Display` and `dyn Iterator` trait objects (which are types).
- lpil 4y agoWhat makes you say that? Traits and type classes don't inherently use either static or dynamic dispatch (Rust and Haskell use both, PureScript uses only dynamic), and both techniques outlined in the blog can be optimised to static dispatch.
- pierrebai 4y agoI disagree with the post. The goal of traits, or any similar mechanism in other languages, is not to implement a given functionality. It is to describe and enforce a common way and API to expose the functionality. To take the example given for gleam, there is nothing enforcing that the conversion to string is called to_string, what are its arguments, how formatting works. So you're left to just-so compatibility for each type. They only happen to be the same by accident, and nothing enforces consistency. Programming language and code in general is about communication between programmers. When I design a trait, or an interface, I'm telling everyone else what are the requirements.
- Supermancho 4y ago> The goal of traits, or any similar mechanism in other languages, is not to implement a given functionality. Note: Saying "similar mechanism" is a tautology for whatever you defined after that.
- jghn 4y agoConstructs like Lisp's CLOS and dd hoc polymorphism can provide the same benefit. You don't need a class/trait/etc enforcing a contract on data. Instead you can keep data and functionality separate, and then prove you have an implementation of function X for data Y. This also allows for easy extension of the data downstream.
- Joker_vD 4y agoBut Gleam is statically typed, you see, and very restrictively. In such languages, typeclasses/interfaces/extensible records/(void*)-passing and -casting shenanigans are the things that traditionally provide the virtual/dynamic dispatch to the programmers.
- gct 4y agoYeah Traits give you more than just the type of the output, they let you specify how it must be called to get that trait, that's a key capability for writing generic code. Sure I can write a to_string() method but someone else will call it ToString() and I can't use it. If we both impl Display then it's all good.
- andai 4y agoI've just built a piece of software and realized it would be a lot better if it could save/restore its state when it restarts. I already have all state isolated in state objects, but they're not plain JSON, they contain instances of classes and things like that. But in virtually all cases I could replace classes with TypeScript interfaces (i.e. just use pure JSON and still get the type-checking benefits). There are some exceptions where classes need to be instantiated, and I'm thinking, instead of writing a bunch of ser/de, I can just make them static, Replace-All `this` to `self` and add a `self` param. Boom, suddenly save/load is a simple as JSON.stringify() and JSON.parse() Then I realized I'd reinvented C (plain old data) and Python (explicit self param). Nothing new under the sun!
- BerislavLopac 4y agoI've realised a long time ago that all of programming essentially consists of two things: data, and procedures that operate on that data. Everything else is in function in one or both of these two things. For example - functions and methods organise the procedures in smaller, more manageable (from human perspective) chunks, types formalise and limit the data to make them easier to transfer and organise, and classes (are supposed to) do both.
- PeterWhittaker 4y agoLike many good posts, this one got me thinking, and elicited a visceral reaction. The idea of traits as types is potentially interesting from a pedagogical perspective, since it gives us another way of thinking about things. But whether it is simply analogous or is in fact homologous is the key question: If simply analogous, then we need to understand the limit of the analogy, where it works, where it fails. If homologous, then we have a powerful tool to draw upon, much like how mathematicians can switch analytic techniques equivalent over whatever category they are working with, or how physicists change coordinate systems to make the math and understanding easier for particular types of motion. I tend to think it is simply an analogy, but I don't yet know where its limits lie. > Really the only difference is that we manually convert from Friend to Display in Gleam, where Rust already knows how to use Friend as a Display.... Instead of a trait, just make a type that implements the generic behavior you want, and then write a function to convert your data-type into your trait-type. Aaaand here's where my gut twists. Why? Why would you do that? Why would you ever do "instead of... -> just" where the compiler will do it for you? Twice in that quote the author espouses writing more code because reasons, and, honestly, I don't think the reason is any better than "Gleam doesn't haven't traits and we're all OK with doing the extra work". That's fine. If that's how someone wants to work, and if their code works for them and is as maintainable as they need it to be, great! I cannot gainsay that. And I'll note that for someone who hasn't yet grokked generics and traits, the Gleam code may be more readable, because things are more spelled out. Sort of reminds me of my early Javascript, before I got comfortable with promises, composition, lambdas, and a few other things: Very readable blocks of code, often many lines long, that I would now replace with a single line. But once one understands traits and constraints, one can write very powerful, and, thanks to how Rust works, safe code that other (oh, I do have to use that term? oh, ok) Rustaceans will grok quickly, code that will likely also be performant. If I can save myself time and screen space AND make it clear to others what's happening AND make it safe and performant, I'm in. (I'll just remember to add comments as to why this is a single line and why that isn't.) (EDITS: Typos)
- jayd16 4y agoI don't know Gleam at all but doesn't this beg the question here... |> iterator.from_list() |> iterator.map(Friend) |> iterator.map(friend.to_display) |> print_things_from_an_iterator() This only prints Friend types. How would you loop through a collection of several types and display them? In other languages they could all implement the interface. Perhaps you could have several to_display functions that handle each type but I'm not sure that's valid in Gleam. It would need runtime type checking. In this example to_display is a member function.
- lpil 4y agoGleam is a strongly typed language like Haskell or Rust, so you cannot have a collection of multiple different types without using another type to wrap them into one.
- __lbracket__ 4y agoAside: Can someone please tell me the name of the coding font in the OP?
- techn00 4y agoCascadia Code
- __lbracket__ 4y agoThank you good sir !
- firstlink 4y agoRust's Display is superior to everyone else's To string precisely because it is stream-oriented rather than allocation-oriented. Allocating f-strings are a major language smell.
- lloydatkinson 4y agoWell a function is like an interface (or trait as newer languages like call them for some reason) with one member. The problem the article seems to totally skip over inexplicably is that the point of an interface (trait) is when you have more than one implementation.
- coldtea 4y agoWell, traits is basically an interface, and an interface is implemented by a type, so yeah.
- Supermancho 4y agoTraits are not interfaces, or they would be interfaces. They include a concrete implementation (the mixin part).
- eternalban 4y agoTraits are behaviors. But they are not called behaviors as that would be too clear. For example, we know that traits are personal "uniquely identifying characteristics" and that behaviors are general cross-cutting categories, but for this "mixin" we opt to use the personal 'trait'. We also know we're not just describing something but also have "implementation", but hey let's not call it behavior and give the game away! One of the biggest headaches in software (historically) is how things have been named. ("Dynamic programming" says hello). What is a trait? Traits: Composable Units of Behavior, 2002 https://scg.unibe.ch/archive/papers/Scha02bTraits.pdf https://scg.unibe.ch/archive/papers/Scha02bTraits.pdf -- "A trait contains a set of methods that implement the behaviour that it provides. In general, a trait may require a set of methods that parameterize the provided behaviour. "Traits cannot specify any state, and never access state directly. Trait methods can access state indirectly, using required methods that are ultimately satisfied by accessors (getter and setter methods). "The purpose of traits is to decompose classes into reusable building blocks by providing first-class representations for the different aspects of the behaviour of a class."
- Supermancho 4y ago> Traits cannot specify any state, and never access state directly That's descriptive of 2002 and is a long time ago, as far as I'm concerned. Traits can modify state with the syntax to modify memory, so this definition is of limited use, imo.
- coldtea 4y ago
- FrustratedMonky 4y agoMight be over-simplifying. Seems like F#'s type system did this.
- simiones 4y agoAs usual, simplicity of this type basically means making the programmer write the boilerplate that the compiler could generate for you. The complexity has to live somewhere, and with "simple" languages, it lives inside your own source code, and that of each of your libraries, instead of living inside the compiler. I'm not sure why this appeals to so many people, but still: it clearly does. So, best of luck to the Gleam team, I hope it lives up to your needs and desires!
- theamk 4y agoThis heavily reminds me of old Microsoft's COM's QueryInterface approach, which acted basically the same: interfaces/traits were types. Your code would be like: CDisplayInterface* louis = NULL; HRESULT hr = friend.Friend("Louis")->QueryInterface(CLSID_DISPLAYINTERFACE, &louis) CheckError(hr) io.println(louis->format(DisplayOptions(...))) basically the same thing as Gleam does, but more verbose and without all that safety. (I think anyone advocating "going back to basics" would benefit from actually going back to basics and spending some time on those systems. I hope I will never have to touch COM in my life again, but my experience working with it was very helpful. GLib/GObject and Linux VFS are other class-like technologies which are worth experimenting with)
- giraffe_lady 4y agoEh this isn't lua. It has pattern matching and a sophisticated type system, that's a powerful base that can get you very far.
- simiones 4y agoStill, no traits means no runtime dispatch, which is a pretty big convenience feature that neither pattern matching nor a sophisticated type system help with.
- shireboy 4y agoThis is a bit of a rabbit trail, but something I’ve been wondering about lots of these new languages. Who uses them and why? I’m genuinely asking here. I have a hard time imagining going to a team or customer and saying “c#/go/java/python/javascript are the wrong approach for this project. We need to all learn this new language and use that instead.” Don’t get me wrong: I’m glad people are innovating, and I get that personal curiosity and growth might be good reasons to build a new language. But I just wonder about the real world uses and actual stories behind new and lease known languages. Why did someone create Gleam and how is it being used in production today? What are the long term goals for it? What would be the argument for using it on a company’s projects?
- deleted 4y ago[deleted]
- myaccountonhn 4y agoWell Paul Graham wrote an article on it (http://www.paulgraham.com/avg.html http://www.paulgraham.com/avg.html). Essentially, I think the people who pick those languages believe the extra stability/speed/whatever is gained from using it beats the drawbacks.
- kerkeslager 4y agoThat really depends on which language you're talking about. An opposite trend I've been noticing is that big languages steal ideas from some smaller language and absorb its community: for example a lot of R ideas have been pulled into Python libraries, resulting in a migration from R to the more capable Python. R's syntax is nicer for statistical computing, but having access to the larger Python ecosystem with slightly worse syntax turns out to be more valuable in the long run. > Why did someone create Gleam and how is it being used in production today? I'm an outsider to this community, so I'd trust someone with inside information over myself, but my outside impression is that Gleam was invented to push Erlang's potential for extremely high reliability even farther. Gleam's strong type system lets you create software that rarely fails, and on top of Erlang/OTP's "let it fail" philosophy you have battle-tested patterns for recovering from failures when they do happen. The use case is for networking software which requires high levels of security, scalability, and fault-tolerance, such as cryptocurrencies and end-to-end encrypted messaging systems (it's used by WhatsApp and I think the NEAR cryptocurrency though I can't find a reference for the latter claim).
- kzrdude 4y agoI usually say my favourite abstraction is the function. :) It's one we use a lot. I use Rust, C, Python.