Am I crazy, or did he just give a really good definition of monads in programming? I think that it benefits by not letting itself get bogged down in Category Theory nomenclature which doesn't actually matter when programming.
Haskell people do talk about monadic vs. applicative combinators that are different by whether you can use the results of a previous step on the next ones. But that doesn't have a direct relation with the actual definition of those.
But yes, if you are teaching a programming language that uses monads to someone, you will probably want to explain the problem they solve, not the actual structures. As most things in math, the structures become obvious once you understand the problem.
I think it is a very practical place to start, especially for programmers who have been thrown into a codebase while still new with monads, because it helps them avoid a common mistake that plagues beginners: accidentally dropping error values on the non-success track. Often you simply want to drop values on the non-success track, and there are convenient idioms for doing so, but just as often, you need to examine those values so you can report failures, by returning metrics on validation failures, by providing the right status code in an HTTP response, etc. Railway-oriented programming is a vivid metaphor that reminds programmers that they need to make a decision about how to handle values on the other track.
Thanks for taking the time to respond.
scene add: sprite;
add: otherSprite;
setBackGround: stage;
start
I'm not sure if it matches the "reuse values from previous computation" but it should since messages will affect the object, you just don't have local variables.So this:
scene add: sprite;
add: otherSprite;
setBackGround: stage;
start
is equivalent to: scene add: sprite.
scene add: otherSprite.
scene setBackGround: stage.
scene start.
In Dart, they use `..` prefix instead of `;` postfix: https://dart.dev/language/operators#cascade-notation var paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
You can model this with monads easily, but it's just one, very limited application of them - monads are much more general.In Scala, a language with OOP heritage and support, plus lots of functional programming features, some of the most common methods you use in such chains are monads.
Basically, if you can convert a `Foo<T>` into a `Foo<U>` by applying a function `T -> U`, it's a monoid. Think `map` or `fold`.
But if you can convert a `Foo<T>` into a `Foo<U>` by applying a function `T -> Foo<U>`, it's a monad. Flattening is "some logic", but not any logic, it's inherent to `Foo<>` itself.
In Erland and Elixir, actors and actor-based concurrency hold the central place in the corresponding ecosystems, well supported by extensive documentation.
In Gleam, actors and OTP are an afterthought. They are there somewhere, but underdocumented and abandoned.
As I understand it, there have been a few "high profile" attempts to bring static typing to Erlang, all of which gave up when it came to typing messages. Your comment essentially confirms my bias, but is Gleam making real strides in solving this, or is it poised to merely cater to those who demand static-typing with curly braces--everything-else-be-dammed?
I agree it's underdocumented but doesn't seem abandoned (has commits in last week)
Go uses preemption now (since 1.14), but it didn't always. It used to be that you could use a busy loop and that goroutine would never yield. Yield points include things like function entries, syscalls, and a few other points.
This seems to be the opposite of pragmatic.
The most pragmatic approach to actors when you're building a BEAM language would be to write bindings for OTP and be done with it. This sounds kind of like building a JVM language with no intention of providing interop with the JVM ecosystem—yeah, the VM is good, but the ecosystem is what we're actually there for.
If you're building a BEAM language, why would you attempt to reimplement OTP?
That's exactly what I mean by this not seeming pragmatic. Pragmatic would be making do with partial type safety in order to be fully compatible with OTP. That's the much-maligned TypeScript approach, and it worked for TypeScript because it was pragmatic.
Now, maybe Gleam feels the need to take this approach because Elixir is already planning on filling the pragmatic gradually-typed BEAM language niche. That's fine if so!
It implements the same protocols and does not have any interop shortcomings.
What you're not seeing with the handle_* functions is all the extra stuff in there that deals with, for example, "what if the thing you want to access is unavailable?". That's not really something that for example go is able to handle so easily.
Instead of
defmodule Robot do
def handle_call(:get_state, _from, state) do
something()
end
def get_state() do
GenServer.call(__MODULE__, :get_state)
end
end
robot = Robot.start_link()
robot.get_state()
just let me write (note the new flavor of def) defstatefulmodule Robot do
def get_state() do
something()
end
end
robot = Robot.new()
robot.get_state()
Possibly add a defsync / defasync flavor of function definition to declare when the caller has to wait for the result of the function.The idea is that I don't have to do the job of the compiler. It should add the boilerplate during the compilation to BEAM bytecode.
I know that there are a number of other possible cases that the handle_* functions can accommodate and this code does not, but this object-oriented-style state management is the purpose of almost all the occurrences of GenServers in the code bases I saw. Unfortunately it's littered by handle_* boilerplate that hides the purpose of the code and as all code, adds bugs by itself.
So: add handle_* to BEAM languages for maximum control but also add a dumbed down version that's all we need almost anytime.
If you really don't like the get_state above, I think it'd make more sense to just ditch it, and use GenServer.call(robot, :get_state) in places where you'd call robot.get_state(). Those three lines of definition don't seem to be doing you much good, and calling GenServer directly isn't too hard; I probably wouldn't write the underlying make_ref / monitor / send / receive / demonitor myself in the general case, but it can be useful sometimes.
In my experience with distributed Erlang, we'd have the server in one file, and the client in another; the exports for the client were the public api, and the handle_calls where the implementation. We'd often have a smidge of logic in the client, to pick the right pg to send messages to or whatever, so it useful to have that instead of just a gen_server:call in the calling code.
JavaScript support looks interesting. Browsing the package repo, I don't see how to tell which packages are supported on Erlang's VM, when compiling to JavaScript, or both. JavaScript-specific documentation seems pretty thin so far?
I wrote Vleam[0], which allows writing Gleam inside Vue SFCs, and the experience was pretty good even without the docs.
You do have to sometime read the source of other Gleam packages to understand how things work, but again -- Gleam is so simple it's not too bad of an experience.
Is the `use` statement blocking (in which case it doesn't seem that useful)? Or does it return immediately and then await at the point of use of the value it binds?
[1]: https://en.wikipedia.org/wiki/Continuation-passing_style
EDIT: I believe prior art is Koka's with statement: https://koka-lang.github.io/koka/doc/book.html#sec-with
One level of callback nesting in a function is totally fine, two is a bit confusing, but if you have many async things going on do you really want 10, 15, 20 levels of nesting? What to do about loops?
I certainly greatly prefer async programming with async/await languages that keep the appearance of linear function execution to stacking my callbacks and having a ton of nesting everywhere
It's syntactic sugar, but the readability is worth it
Since it's a callback, I assume it's up to the function whether to call it, when to call it, and how many times to call it, so this can implement control statements.
I would guess that it also allows it to be async (when the callback isn't called until after an I/O operation).
1: https://livescript.net/#:~:text=Backcalls%20are%20very%20use...
In F#, “<>” is the equivalent of “!=“. Postgres also uses <> for inequality so my queries and f# code have that consistency.
Erlang and Elixir don't overload the `+` operator. In fact, they don't overload ANY operators. If you can forgive the syntactic choice of the operator itself (which I think it pretty fair considering Erlang predates Postgres by a decade and F# by two decades), this allows them to be dynamic while maintaining a pretty high level of runtime type safety. For example, one of the "subtle bugs" people refer to when criticizing dynamic languages (even strongly typed dynamic languages) is the following would work when both args are given strings or numbers:
function add(a, b) { a + b }
Erlang/Elixir eliminate this particular subtle bug (and it goes beyond strings and numbers) since: def add(a, b), do: a + b
will only work on numbers and raise if given strings.Interesting table here highlighting old programming languages https://en.wikipedia.org/wiki/Relational_operator#Standard_r...
Shouldn’t copy Erlang, otherwise might as well use it.
What do you mean by "it's use in mathematics"? To my knowledge <> was invented by Algol language creators to use it for inequality. There was no previous use in mathematics. And to my opinion, that was an unfortunate error.
The plot thickens, apparently ++ is used for erlang. So I still find it a poor choice.
Really though who cares? `=` is already misused in most programming languages.
The solution to type confusion is not separate operators for every type, it's static types!
I don't want to get into it but Erlang is dynamic by design. There have been several attempts to bring static typing to it over the years which have failed. People are still trying, though!
Erlang doesn't use <> for concatenation so it's odd to name it in this comment, like that language and its developers have anything to do with your complaint. If it upsets you so much, lay it at the feet of the actual groups that chose <> for concatenation instead.
- in Elixir <> is Binary concatenation operator. Concatenates two binaries. This seems like it might be kind of a joke, actually, purposefully confusing "binary operator" with "an operator that takes two binaries" for humorous effect?
- in Gleam <> is string concatenation operator
As far as I can see it, they are taking inspiration from Haskell, where <> denotes the monoid binary operation, one concrete example being in the monoid of Lists binary operator being list concatenation, of which String is one example.
But really, <> for inequality is also kind of dumb and nonstandard idea (from mathematical notation perspective), originating from Algol. != which C popularized is more clear, and corresponds to the mathematical symbol, of course =/= would be even more close, but that is one more character.
ML originally used <> for inequality, following the standard (in CS) of Algol, and it was Haskell which deviated from that tradition. So F# uses still Algol tradition, but Haskell uses /= and C and others use !=, for more mathematical and logical notation.
Also, <> was != in BASIC, I believe.
PS: Don't paste this comment in your shell.
Elixir uses <> as an operator for concatenation of binaries, (which does form a monoid of course), not to be confused with how Haskell uses <> as a binary operator of a Monoid, but for sure inspired by it. And Gleam picked it up from them, probably, to use for a special case of a list monoid, String. And Haskell created <> for Monoid, because it would be too confusing to use multiplication sign for the binary operation like mathematicians do. It would not be ok in programming context.
let spawn_task i =
async {
let n = string i
printfn $"Hello from {n}"
}
// Run loads of threads, no problem
seq { 0..200_000 }
|> Seq.map spawn_task
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
The two are pretty similar, but I would give F# the nod on this one example because it doesn't actually have to create a list of 200,000 elements, doesn't require an explicit "main" function, and requires fewer brackets/parens.[0]: https://gleam.run/
I’d like to dig into the OTP library (I’m curious if anyone has worked with it much?) and create a state chart library with it, but I’m still firmly in the “I don’t totally get it” camp with a few parts of Gleam. I don’t deny that it’s pragmatic. Maybe it’s more so that I’m not up to speed on functional patterns in general. I was for years, but took a hiatus to write code for a game engine and supporting infrastructure. It was so Wild West, but I kind of liked it in the end. Lots of impure, imperative code, haha.
Functional programming seems too limiting and OTP seems more complicated than I would have hoped for a supposedly distributed concurrency system.
I'm sure it's just a skill issue on my part. Right now I'm way too rust-brained. I've heard lots of things about gleam being good for productivity but I don't feel unproductive writing web apps in Rust but I felt every unproductive trying to write a non-trivial web app in gleam
For Gleam I was trying to write the whole FE + BE in the same language - I really like that it can be compiled to JS, and I'm honestly sick of the whole React + seven thousand dependencies game, so I was using Lustre (an Elm-like library for Gleam). And again, I've programmed an app in Elm, after a lot of hair pulling, and in the end I didn't enjoy it that much.
I've gone through tutorials and I don't understand things like types having different wildly unrelated constructors, currying (I didn't notice much currying in Gleam but really disliked it in Elm, I cannot follow past the first or second arrow). For writing the front end of the app, I would make _zero_ progress unless referring to other Github projects (and it was hard to find any since Gleam was so new). Anyway, if someone has a book or something that can teach me this stuff it would be great. I want to use the OTP and a single language for FE/BE that's not JS. I'm not dumb, I've been programming since I was a little kid, but maybe I'm too stuck in imperative models.
And if you feel like you're stuck and need help Gleam's Discord is a great place to ask questions :)
Maybe a generic “pile this data into this value and pretend it’s safe” tool might be nice for prototyping.
@deriving(json)
class Person:
id: int
name: str
Would give you something like: def parse(s: str): Person: ...
def print(p: Person): str: ...
record User(string Name, DateOnly DoB);
[JsonSerializable(typeof(User))]
partial class SerializerContext: JsonSerializerContext;
...
var user = new User("John", new(1984, 1, 1));
var response = await http.PostAsJsonAsync(
url, user, SerializerContext.Default.User);
Despite it being easy to use, I find I inevitably wind up requiring a lot of ceremony and effort to ensure it’s safe. I’m not a huge fan of automatic serialization in that it appears to work fine when sometimes it shouldn’t/won’t. I agree that it’s a lot of effort though. I guess the question is if you want the effort up front or later on. I prefer up front, I guess.
In the JavaScript space, Effect offers an awesome package for ser/de which integrates validation. I think it’s my favourite tool in the ecosystem, but I prefer it over options in many other languages as well.
I wish Gleam would implement some kind of macro system, making a serde-like package possible.
Does a Gleam programmer in practice need to deal with Erlang? Do Erlang error messages leak through?
What kind of cases? Were you already proficient in Erlang and its ecosystem?
Sure! For one of my most used packages (https://github.com/giacomocavalieri/birdie) I needed to get the terminal width to display a nice output, that has to be implemented using FFI based on the specific runtime (erlang or js) so I had to write it in Erlang, that was just a couple of lines of code.
But now there's a Gleam package to do it, so if I were to rewrite it today I wouldn't even need to write Erlang for that and could just use that!
> What kind of cases?
Usually it is when you need some functionality that has to rely on specific things from the runtime (like IO operations, actors on the BEAM, async on the JS target, ...) and there's no package to do it already. Most of the common things (like file system operations and such) are already covered
> Were you already proficient in Erlang and its ecosystem?
Not at all :) I knew very little about Erlang (basically nothing behind the syntax), Gleam was my introduction to the BEAM ecosystem and it has worked out great so far!
Hope this is helpful, happy to share my experience here
In practice runtime errors in Gleam are rare. The one place you'll likely have to deal with poor Erlang errors is if you are writing Erlang code to create Gleam bindings to an existing Erlang library.
With Go it shares a lazer focus on simplicity and preemptive channel-based concurrency. But of course for all the above reasons listed above it looks very different from Go in most other ways.
In many way its language choices are the opposite of Python (static types, immutability, massive concurrency is the norm).
```
import gleam/dict.{type Dict}
import gleam/int
import gleam/io
import gleam/result
import gleam/string
```
What's wrong with a standard library the bits of which you want you choose to import?
That's not importing the types, it's importing a suite of functions related to the types.
https://hexdocs.pm/gleam_stdlib/gleam/int.html - gleam/int for example. The int type is already in the language and usable, this import brings in some specific functions that are related to operations on int.
As mentioned in this thread, having to import libraries to operate on basic types is suboptimal to say the least.
One of Gleam's design goals is to not have multiple ways to do the same thing, so having to pick between using method chains or pipelines would work against that.
> having to import libraries to operate on basic types is suboptimal to say the least.
The language server will do this for you in a Gleam project.
Folks recommended tools to alleviate Java verbosity back in the day as well. But you still have to read it—which unfortunately happens 100x more than writing.
Totally. Gleam priorities reading over all else, and generally it is praised for being unusually easy to understand code written in it
I suppose we should agree on what "pragmatic" even means, since it has become something of a cliché term in software engineering. To me, it roughly means "reflective of common and realistic use as opposed to possible or theoretical considerations".
So is having to import basic functionality a pragmatic design? I would argue no. Having to import basic functionality for integers, strings, and IO is not pragmatic in the sense that most realistic programs will use these things. As such, the vast majority of ordinary programs are burdened by extra steps that don't appear to net some other benefit.
Importing these very basic functionalities appeals to a more abstract or theoretical need for fine-grained control or minimalism. Maybe we don't want to use integers or strings in a certain code module. Maybe we want to compile Gleam to a microcontroller where the code needs to be spartan and freestanding.
These aren't pragmatic concerns in the context of the types of problems Gleam is designed to address.
To give a point of comparison, the Haskell prelude might be considered a pragmatic design choice, as can be seen from the article. It is a bundle of common or useful functionality that one expects to use in a majority of ordinary Haskell programs. One doesn't need to "import" the prelude; it's just there.
I don't personally find Gleam's design choice a bad one, and while GP was a bit flippant, I do agree that it is not an example of a pragmatic design choice.
When the rule is "if you need a module, you must import it", and that applies equally to standard library modules, hex packages or your own internal modules, there are fewer mental overheads. The procedure is always the same. Incidentally, this also means that the Gleam language server can automatically add or remove import statements, which it now does [0].
Personally, I also find it pleasing that I can look at the top of a file and say "oh, this module appears to be doing some stuff with floating point math and strings". It often gives me an overview of what the module might be doing before I begin reading the detail.
[0] https://gleam.run/news/convenient-code-actions/#missing-impo...
Other Gleam libraries will use other namespaces.
These took me basically no time at all to find. Are you looking for something else for partial application?
> This is not how it works in curried languages, however. In curried languages with a |> operator, the first expression still returns "Hello, World!" but the second one returns "World!Hello, " instead. This can be an unpleasant surprise for beginners, but even experienced users commonly find that this behavior is less useful than having both of these expressions evaluate to the same thing.
The upside (on the curried side) is that you can define `|>` as a normal function (even without lazy semantics, as in OCaml.) How much of an "upside" this is will vary, but note that this generalizes to many other operators that can be added. The rest is a matter of API design, i.e., the order of arguments and the use of named arguments (and/or other syntax sugar.) For example, in the case of the post's example:
"Hello, "
|> Str.concat "World!"
You can get the "beginner friendly" semantics with just a little change to the `Str.concat` (assuming named args support, using OCaml syntax): "Hello, "
|> Str.concat ~rest:["World!"]
In non-curried languages, this has to be a macro (or it needs to be built-in). If you already have macros in your language - that's good: you can easily make `|>` do things like `x |> func(arg1, _, arg2)` and more. However, if you make this a special case in the language, it will be hard to extend and impossible to generalize. So personally, I'd grade the options in order of power and convenience: - infix macros
- infix functions in curried languages
- special, hardcoded |> operator
- beating your language black and blue until you get a work-alike (like with implicits in Scala)
There's also a special category of languages where pipelines are primitives (shells, jq), but that is outside the scope of this comment, since they are more than syntactic sugar for function application :)