30 ms·
Fair point! That is a recent update of Haskell, though (one I wasn't aware of when designing the case syntax). It does agree with my point, though, right? > Th
by LangMakers 5y ago
Fair point! That is a recent update of Haskell, though (one I wasn't aware of when designing the case syntax). It does agree with my point, though, right?
> The main problem with `do`-inspired monadic notation is that it forces monadic code to always be in A-normal form, which is pretty annoying. It'd be far nicer to allow for monadic code to look the same as normal code, just with say an extra brace (such as "IO { }") to indicate that you're inside a monad.
I agree with that. Not sure how that could work, though. Idris had an interesting syntax, but IIRC it wasn't general.
- dwohnitmok 5y agoWell `RecordWildcards` has been around for 14 years... but even without it instead of `{..}` you'd just have `_`s. The main thing that is different is that your Kind example had nested case statements while your Haskell example tried to match everything on one shot, which makes for a non-equivalent comparison, and you purposefully repeated field names on both sides of `->`, which is not necessary even in vanilla Haskell when you work with records. > Not sure how that could work, though. Idris had an interesting syntax, but IIRC it wasn't general. RE Idris I assume you're talking about idiom brackets for applicatives? The general syntax is given in something like https://github.com/monadless/monadless https://github.com/monadless/monadless. The idea is to basically take async-await syntax and generalize it to any monad. So e.g. your `Maybe` example (using `!` for the equivalent of `await` for concision) would look like Maybe { x = !list[0] y = !list[1] z = !list[2] x + y + z } but because it doesn't have to be in A-normal form anymore you can compress it all into a single line. Maybe { !list[0] + !list[1] + !list[2] } So you can take any normal code you have and put in a monadic context simply by inserting brackets around the whole thing and then sprinkling in `!`s as necessary. EDIT: Or to think of it another way, just move the `get` from the left-hand side of `=` to the right-hand side. I'd recommend renaming it to something to indicate that this is a syntactic transformation, rather than a normal function (which is why I like something like `!` or anything else that has a symbol in it to indicate this is essentially a macro).
- LangMakers 5y agoOh, ok, then I was ignorant about {..} - sorry! You still need to rename every field you need, though. So Haskell is, at best, comparably verbose, and, at worst, much more. It also kind of encourage one-letter variable names. About the monadic syntax, that is really clever, and makes a lot of sense to me. I want this implemented, now. Will keep that in mind. (And accept PRs!)
- lmm 5y agoHaving used various monadless-style implementations I think they're a mistake. The whole point of using a monad is because you want to explicitly sequence some (generally noncommutative) effect and be sure that refactoring won't change the behaviour (whereas if you reorder two `<-` lines then that is a behavioural change that you need to test). If you allow ! at arbitrary points in an expression then you end up with something more akin to checked exceptions where you know that an effect is happening somewhere but not what all the possible code paths are, and at that point you might as well just use side-effecting code.
- dwohnitmok 5y agoYeah this was the same discussion that happened when monadless was released in the Scala community. I personally don't see much of a difference between reordering `<-` lines and reordering !-ed expressions, especially in a strict language (such as Kind). The same arguments for how you rearranging different !-ed expressions can result in different behavior whereas non-!-ed expressions can be reordered with no change ring exactly analogously to me for `<-` statements vs the usual `=` in a let expression. The fact that val x = something val y = somethingElse doThis(x, y) allows for interchanging x and y if `something` and `somethingElse` are pure but for { x <- something y <- somethingElse } yields doThis(x, y) doesn't allow for interchanging `x` and `y` feels approximately just as confusing (or not-confusing) as x = something y = somethingElse doThis x y having interchangeable `x`s and `y`s but MyMonad { x = !something y = !somethingElse doThis x y } not having interchangeable `x`s and `y`s. I suppose what you're really getting at is what happens when you combine it all into a single line a la MyMonad { doThis !something !somethingElse } but I still don't find it confusing for a strict language (and besides legions of developers have had a chance to familiarize themselves with this via async-await). The rules in either case are the same. If you move around `->`s behavior might change. Likewise if you move around `!`s behavior might change. In a lazy language such as Haskell I agree that it can be rather weird to have to think about how inserting a pair of brackets and some `!`s around suddenly causes sequencing that is different from how a normal statement is evaluated (although the bangs are quite suggestive, especially given their lineage in Haskell, indeed given a `Lazy` monad these bangs just become the same thing as bangs in Haskell, so another way you can view this is as a generalization of Haskell bang patterns). But Kind is a strict language AFAICT and in a strict language evaluation from left to right and top to bottom is the norm. And that left-to-right, top-to-bottom order immediately tells you what the code paths are. Even in a lazy context where the order of evaluation might be a bit more confusing, this async-await style of writing code is still valuable for delineating which code blocks are side-effectful are and which aren't. Checked exceptions/checked effects are still very valuable! It's valuable enough that e.g. the Haskell community is perfectly willing to compromise on knowing what all the code paths are with stuff like ApplicativeDo. Having spent a considerable time with cats-effects in Scala and a little time with Kotlin's Arrow Fx there are times I still prefer Arrow Fx despite the general clunkiness of Kotlin's Arrow ecosystem over cats-effect precisely because I just need to stick in a `suspend` instead of having to contort perfectly fine code into a for-comprehension when I introduce some effects simply because I didn't write my pure code with the Identity monad the first time around.