re: Elm 0.19 Broke Us 💔 VIEW POST

VIEW FULL DISCUSSION
 

I'm sorry that your experience with the community has been negative.

To be fair about the operator issue, Evan has removed a couple of operators from the std-lib in 0.19, but kept a few that makes a ton of sense in web development (like </> for url path separators). Experience is that a name is much better than an operator except in the few cases where an operator is universally known.

Out of curiosity, what sort of cases are you not able to do with ports or custom elements and must use native code to do?

 

The problem with this "Experience" based stuff is that it only considers a few use cases.

Elm would be great for us if it stuck to FRP and added meta-programming. We generate a lot of the UI at runtime instead of having hardcoded UI structure, this is basically impossible to work with in Elm.

Just because Evan doesn't write complicated code, where a symbol would make things more comprehensible, doesn't mean nobody in the world should.

 

"this is basically impossible to work with in Elm"

Then you should use a language which allows this. Elm doesn't try to be everything to everyone, and if it doesn't suit you, you shouldn't expect it to change to suit your needs.

We surely did move to another language.

However, I have used Elm since very first release and have seen it deteriorate. In the never-ending effort to "stay friendly to beginners", many important features are not implemented or removed in later versions.

Informed discussion about it is also not really possible, in particular because of typophobic comments like yours: "go back to your own country with your Fancy Types".

There is a certain glass ceiling in Elm that everyone eventually will run into...

And also, please note that people are not suggesting totally crazy shit, like asking Evan to add linear dependent types or something.

Think about it: we have theoretical tools for UIs such as reactive programming & profunctor optics, which are perfect for advanced UI programming and which would be so awesome to have in Elm. They would make many many things much simpler...

See, I've used Elm for a long time too and in my opinion it has only improved. While the features you want might seem important to you, they don't seem that way to me as I've yet to experience that I'm blocked on some issue because of a missing feature in Elm.

While I'm aware that there are things Elm doesn't suit for because it might miss some feature or another, I would personally expect Elm to incorporate features once it has a good design for it, that doesn't deteriorate some of the best things about Elm (like being a small language that generally does what you'd expect and gives great error messages when something is wrong).

 

Thanks for your comment Robin.

I read the post on operator removal. Did you happen to read my response? Some of your observations are addressed there. So the interesting bit about that is: if you read how the URL parsing library came about, the idea was based from another language. That's also how the custom operators </> and <?> were brought into Elm. First as a personal library. And now an Elm package, enshrined as exceptions to the "no custom operators" rule.

Without custom operators, this same discovery and experimentation process is now blocked to ordinary Elm users. Were someone to discover a new use case that an operator would be great for, they can't choose to use it themselves or experiment with it in Elm. They would have to beg for an exception.

Their removal also implies that whatever custom operator you were using for a specific use case (not a general solution) was wrong and you can't have it anymore. There happens to be significant prior art that says otherwise especially within the family of languages that Elm descends. I came away from Evan's post thinking there is no clear problem with the custom operators, but he decided to take them away -- knowing users are using them -- anyway. This is a pretty intense way to deal with your users.

I might have been a little overwrought with the "no other reasonable way" comment. There is usually always a way to do something, but it might be too painful (hence unreasonable) for us.

The primary thing Elm fails to do for us right now is type-based JSON de/serialization. Elm's built-in encoding and decoding is great for fuzzy APIs. However, that is not a situation we find ourselves in. For us encoders and decoders are an intractable solution which creates at least hundreds of lines of redundant/difficult code to maintain. Instead of that, we define a port, and call the 20 lines of native code to extract the auto-generated encoder or decoder from the port. The alternative is to invest in creating an external code-gen tool for encoders and decoders. Another tool to maintain and be familiar with.

The second usage we currently have is for file uploads. Elm has no built-in support for this. For now I have 20 lines of native code that converts the javascript File into something that the Http module will send. Basically, it smuggles the File(s) thru Elm as a StringBody. Http module just calls xhr.send under the covers, so it works fine. I did recently find an Elm only solution to this that might work.

I had every intention of switching away from these native solutions once Elm has fleshed out something better. But in the interim, native was my way to fill in missing things myself. Now that avenue has been removed, and I wouldn't characterize Elm's API complete. Worst of all, the main reason it was removed was just because Evan didn't want ordinary Elm users to have it... just blessed Elm package devs. It makes me sad.

 

I did recently find an Elm only solution to this that might work.

You got me curious, what are you thinking about?

Well, I thought maybe I could make it work when I found this function which basically decodes the event.target.files[0] into a Value. The MIME type should also be available by decoding the type property off of this.

However, the only way to convert a Value into a Http.Body type currently thru Elm is with jsonBody. And when I traced that all the way down, it is going to fail to work for 2 reasons. First, jsonBody sets the MIME type to "application/json" always. Even if you set the content type in a header, StringBody's content type is evaluated last so it will probably overwrite or concatenate whatever else you set with "application/json". (This is a problem for my use case anyway.) Second, jsonBody calls Encode.encode on the Value which calls JSON.stringify in javascript. In my quick search, this will neither stringify the file contents nor keep the JS File object in tact. So it will not upload the file.

So, nevermind. I still need to do it with native code (edit: or a port to Javascript) currently.

For the MIME type, this is not a problem, you can define your own function like:

rawBody : Decode.Value -> Http.Body
rawBody value =
    Http.stringBody "application/octet-stream" (Encode.encode 0 value)

Encode.encode is another story. JSON.stringify could be monkey patched to return File objects unmodified, but encode is actually JSON.stringify(...) + '' (since 0.19), so even if stringify preserves the file, encode will still return an "[object File]" string because of type coercion.

Anyway, using a port with readAsDataURL and uploading base64 encoded files is not so bad, are the overhead and server decoding really an issue for you?

Anyway, using a port with readAsDataURL and uploading base64 encoded files is not so bad, are the overhead and server decoding really an issue for you?

Nah. At the time I couldn't find a succinct example with the FileReader stuff. The one I found still used native code and also had a lot of extra abstractions to wade through. So it was quicker to just do what I did. But I'm not married to it. I did find a pretty clear port solution with FileReader that I would use now.

 

I am sorry the new release complicates things for you. However, nicer solutions may be at hand. Have you ever considered using OpenAPI to define your models/APIs? Using the OpenAPI generator you can easily create all models, encoders and decoders automatically. We use it to define our APIs and generate both backend and frontend code from it.

 

Well.

I can't disagree with you that experimentation with operators is now harder. But the reason for their removal is that operators is very rarely more readable than named functions. While you're right that there is a lot of prior art when it comes to good custom operators, most of the custom operators in Haskell (for instance) are, imho, horrible.

Operators can make reading code easier, but only when everyone immediately understands what the operators mean and this is only true for a very small set of operators. When Evan decided to remove custom operators it was because he scanned through the packages repo and found operators like -~>... what does that even mean?

Regarding the "this is a pretty intense way to deal with your users." comment, we need to focus on what Elm 1.0 should look like. At that point, things like this won't change anymore, but before that point Elm should remove things which isn't necessary to the language. That's the only way to make it easier for professionals and beginners alike.

For JSON encoding/decoding, I understand your pain as I initially thought about them that way. But I've come to view them as assertions that what I'm given from the backend (or browser) as correct, and makes me write code that deals with them if not. I also use them to convert certain constants into union types. Besides, encoders/decoders rarely change, so they're low maintenance. There are several code generators for encoders/decoders (like this one noredink.github.io/json-to-elm/), but I've not really felt the need for them.

File uploads is something that is lacking from Elm, and I hope that it's something coming soon. However, you don't need native/kernel code for this. A port should work just fine for this use case.

A lot of opinions here that are different from my experiences. Some people use odd operators. We don't -- just the common ones for bind and map. But even if they do, who cares? It's not likely to be used when people are intimidated by the operators. It's a freedom that really didn't have to be taken away. Some people really need encoders/decoders. 99% of our API calls don't -- the types always line up exactly, and we do adjust types regularly. So maintaining co/decs has only downsides.

For files, I agree a port is perfectly fine. At the time I could not find a succinct example for that, and it was just quicker to do what I did. But were I doing it again, I would probably use this as a guide.

They're not my opinions, it's my experience. I came to Elm from languages which much more constructs. Nowadays I spend most of my time in languages which are very small (Elm, Go...). I don't miss the days of C++, C# or JS.

"It's not likely to be used when people are intimidated by the operators." <--- This is wrong. If there's really only one library which provides what you need, and that library uses operators, you have to use them. If you're fixing bugs some other developer wrote, and that developer is fond of operators, you have to use them. There are other situations as well, but the idea that people who don't like a construct will avoid it, just doesn't work out in practice.

Operators, while nice in certain cases, doesn't enable something that wasn't possible before. Unless they're universally understood, writing a function with an easily understood name that can be used in a pipeline is just much easier.

I can agree with you that operators can make code more cryptic and should be used sparingly. But I disagree that everyone's choice about using them should be taken away. It would be one thing if they were blocked from published packages, but they are not even allowed in my app code now. That's just being controlling.

Your perspective is valid, but (my own opinion, not necessarily Evan's) since Elm is still pre 1.0, I think the perspective from the language creator is not "what can I take away" but "what should be in the language when it's done."

So while the users of the language in the current state feel som pain through breaking changes, I think Evan has a 20 year perspective on things. And in 20 years, will Elm programming be worse or better off because of custom operators? We can disagree, but my experience with Haskell is that Elm is better off without.

But yeah, I get that it's frustrating that a feature you use is removed.

Ah! That's the difference. I'm coming from F#, which has custom operators. But it has not really gone overboard with them. Without HKTs/typeclasses it is not easily possible to define an operator once and have it apply to different types. You have to define the operator for every containing type, and then you can end up with naming conflicts if you overdo that.

So in practice, the operator really has to buy you something to bother, and it stays limited to a specific module. Probably the worst offenders I've seen are libraries ported from Haskell. But since they are limited to that library, I can just use a different alternative, or in small cases just write my own. So my experience with custom operators has been drastically different, and I have found them useful in F#. For example, I ported Elm's URL parsing library to F#, and because custom operators were allowed was able to use the </> and <?> operators. If I find myself in the reverse situation, I would be stuck in 0.19.

But yeah, I get that it's frustrating that a feature you use is removed.

I use this feature in Elm almost not at all, currently. I could easily get by without it. (But I have needed it in F#, so view it as useful and a mistake to remove.) As mentioned in the original post, it's the overall message that we're getting from Elm that has me reeling. Not just this one piece.

I wholeheartedly agree with this.

"what should be in the language when it's done."

People have been suggesting countless of things, type classes, SYTC, HKT, etc etc... those issues get closed with comments such as "use another language then" or "almost nobody uses this".

I am actually suspecting that nobody really knows how to implement these features on a theoretical level and that's why...

The implementation for these things are known, there's just no pressing need to introduce them to the compiler. There's also no consensus on what the right implementation would be.

I write Elm full time, and don't miss these things. There have been times where it would be nice to implement comparable for one of my types, but I'm not convinced that type classes is the right approach for ad-hoc polymorphism in Elm.

 

I'm a bit late, but I have a really good use for custom operators. I had a project that dealt with types, so I had a custom arrow operator so I could write functions like a => b => c. I also had operators related to composing lines of code that my program generated.

In my opinion disallowing Javascript is the correct choice because Javascript modules can be security threats and not having Javascript makes Elm libraries usable when compiling to something else.

On the other hand disallowing use of harmless expert features is stupid. Custom operators are a feature that beginners don't need to know about. Removing it is like removing plus and minus.

The stated reason that naming operators is hard could be solved by giving back the ability to call functions in an infix position or allowing overloading of functions. The latter would also get rid of appendable, which in turn would get rid of the ridiculous compappend.

I even found a bug related to this by just glancing at the implementation one day. github.com/elm/compiler/issues/168...

 

The problem is that beginners can't simply ignore them. They're usually faced with them because someone on the project thought it was a good idea, or because the only open source library which provides x also makes heavy use of operators.

And that's the thing about operators: when you have a good operator it makes the code easier to read, when you have a bad one it makes the code much harder to read. I personally only really like the piping operators, but then again I'm a Lisp guy.

Overloading functions makes things harder to understand in general (wait, which version of that function is called now?), and is difficult to combine with currying.

My point is that beginners already have to deal with operators. Adding some badly named ones is not worse than adding badly named functions. I have taught beginners and have found that they are confused by the fact that Map and Array do not have syntax associated with them.

If someone is able to use plus with prefix notation, I'm not convinced that custom operators are a big hurdle anymore.

Making your own overloaded functions is harder to learn than (comparable, appendable, number) but without overloading there is no way of making custom comparable types.

Using overloaded functions requires you to understand errors like "No version of (+) takes an argument of type Model." The current model requires understanding of comparable and appendable.

Overloads would make complex type mismatches worse, but those are not understandable anyway unless you add type annotations.

The real reason for why overloading wouldn't work is a technical one. For example a set data structure needs to work with any comparable type and the definition of (<) has to be visible to the module implementing a the set. If this could be elegantly overcome, I think overloading would be the type class replacement for Elm.

Getting rid of appendable would just require the removal of ++ for Strings, though.

Overloading [...] is difficult to combine with currying.

I think it makes sense to require that the functions have the same number of arguments.

My point is that beginners already have to deal with operators.

Beginners usually only deal with operators they already know, like +, -, * and /. As far as I've seen, most people don't use operators in prefix notation.

I'm not convinced that custom operators are a big hurdle anymore.

There's a big difference between + and -~-> though (and yes, that operator existed).

Getting rid of appendable would just require the removal of ++ for Strings, though.

Here we agree, though I'd rather remove ++ for Lists as ++ is really great for String concatenation.

I think it makes sense to require that the functions have the same number of arguments.

You'd still get a problem with stuff like

someFunc : (a -> b) -> a
someFunc : a -> a
code of conduct - report abuse