typelead / eta

The Eta Programming Language, a dialect of Haskell on the JVM
https://eta-lang.org
BSD 3-Clause "New" or "Revised" License
2.6k stars 142 forks source link

RFC: The Eta Prelude #617

Open NickSeagull opened 6 years ago

NickSeagull commented 6 years ago

As explained on #308 , we'd probably have a custom prelude for Eta.

I'd like to suggest that we could put some major changes on it that make things easier for newcomers to Eta/Haskell, but make it optional for people that already come from Haskell.

For instance, I'd really like the following changes for some operators:

Most of these operators are great for writing in a fluent style, which coming from Java, is very natural:

foo :: a -> IO String
foo myParameter =
    myParameter
    |> doStuffThatReturnsIO
    |$> length
    |$> show

Also, I think that it'd be great if we had:

It'd be great if we used this opportunity to remove all the non-sense from the Haskell Prelude and made adoption easier coming from other languages :smile:

rahulmutt commented 6 years ago

@NickSeagull Completely agree with almost all of this. If possible, can you start a proposal on eta-proposals where you describe all the functions that should be present in the prelude. Include open ended questions on the parts you're unsure about and we'll work it out over time.

The plan moving forward is that we'll have a new .eta extension which automatically turns on a lot of Haskell's language extension that have proven to be useful defaults (documented here). If you send a .hs file to the compiler (i.e. when compiling Hackage packages) it'll go back to GHC's defaults.

NickSeagull commented 6 years ago

Love the fact that we will have our own extension! Will create the proposal, this deserves a thorough thought, though :smile:

puffnfresh commented 6 years ago

I teach Haskell. I think this would cause problems with my team learning/using Eta and I don't think there's evidence that it would be useful.

tonymorris commented 6 years ago

I don't agree with this. There is an important order of arguments to (<$>) and (<*>) that makes one preferable over another (this is a rabbit hole). The name (<*>) comes from the original paper, Applicative Programming with Effects. To flip the order of these arguments, is by definition, flipping the correct order of arguments. That is not to say this flipping is not useful, but that it is occurring.


read should be derived from one direction of an IndexedPrism. From this, the function String -> Maybe a will fall out.


FYI, the type-class hierarchy in Purescript came mostly from Scalaz, which is still a WIP, though has mostly stabilised compared to what we had prior. Not sure if that might help.


Finally, I don't think "natural in java" is a good reason for anything. In the early 2000's, after I'd been working on the JVM, my colleagues were arguing with me over "what is natural" (a simple logical fallacy on its own), so I just wrote the code: http://functionaljava.org/ and asked, is this natural? None of them use Java anymore.

jneira commented 6 years ago

I think eta needs to be careful about changes that makes difficult to projects be compiled with different ffi backends (ghc/c, ghcjs/js, eta/jvm). So i would try to avoid default substitutions and deletions respect to the actual haskell standard prelude. And i would try to make additions (including alias) coherent with the actual constraints and naming conventions. Even with a different file extension i think it is important that you could be able to write standard haskell code in a .eta file and it should work without change the default config. What do you think about add the operators as alias of existing ones instead of replacing them?

NickSeagull commented 6 years ago

I'm not talking about removing the actual prelude, but rather have a mandatory import Eta.Lang with a NoImplicitPrelude extension by default @jneira :smile:

Regular projects would still compile. Its the same as using Foundation or ClassyPrelude in a Haskell project

NickSeagull commented 6 years ago

@tonymorris , didn't know about that PS was derived from Scalaz. Although I now refrain from removing stuff, I still suggest to have special operators. In the end the majority is the one who dictates the norm, and we should adapt to that in my opinion.

rahulmutt commented 6 years ago

@NickSeagull I agree with @jneira that existing functions/names shouldn't be changed, but aliased when possible. And for you suggestion read :: String -> Maybe a, we should probably name it readMaybe instead to avoid changing the type of an existing function.

@puffnfresh There isn't any evidence, but the style of writing code (ordering) does matter. My personal thoughts on this are that when transitioning to functional programming, people already have to expend energy on learning the new abstractions (monads, etc.) which is worthwhile since this knowledge ends up being language-agnostic.

But I'm not sure trying to re-train their brain from fluent-style is a worthwhile expenditure since it's just about style and not about content. Of course, if there are benefits that are worth the expenditure that I'm not seeing right now, I'd be happy to listen.

If functional programming was widely taught in colleges, I would agree that it makes no sense to change the order from the "correct" one, but that is sadly not the case and won't be for a long time. Therefore, we pretty much have to assume everyone will have the imperative/OOP mindset starting out and work from there.

@tonymorris While I do agree that order of arguments affects how you can write compositional pipelines, can you provide concrete examples where @NickSeagull's suggested order proves to be inconvenient?

As discussed, we will not be mutating the existing Haskell prelude, but layering on top, so at the end of the day, you don't have to use the new aliases in your own application code, but we will most likely encourage usage of the new aliases for packages that are purely meant for Eta alone and in all Eta learning material.

As to your argument about "natural" I completely agree. But see the argument above about energy expenditure. Moreover, it's not just about Java - F#, Elm, and many other functional languages have followed that order to such an extent that even programmers with experience in these languages will sort of expect it.

puffnfresh commented 6 years ago

Does a library like this already exist?

tonymorris commented 6 years ago

@puffnfresh library like which?

If the goal is "to do better than Haskell prelude", then

a) there is a huge opportunity here to do well b) there is a chance to screw it up, let's not!

NickSeagull commented 6 years ago

@tonymorris I'd love to accomplish that goal, but also, on the other hand I'd love to see helpers for newcomers to Haskell, like @rahulmutt noted in his comment

@puffnfresh there is flow by Taylor Fausak, which is absolutely lovely, but I'd like to see more stuff included

tonymorris commented 6 years ago

I'm teaching in Melbourne next week, and Brisbane the week after. See @queenslandfplab on Twitter. You coming?

On 27 Jan. 2018 22:42, "Nikita Tchayka" notifications@github.com wrote:

@tonymorris https://github.com/tonymorris I'd love to accomplish that goal, but also, on the other hand I'd love to see helpers for newcomers to Haskell, like @rahulmutt https://github.com/rahulmutt noted in his comment

@puffnfresh https://github.com/puffnfresh there is flow https://github.com/tfausak/flow#readme by Taylor Fausak, which is absolutely lovely, but I'd like to see more stuff.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/typelead/eta/issues/617#issuecomment-360982403, or mute the thread https://github.com/notifications/unsubscribe-auth/AAM2fn2ag6tKazToz9tRlZg-oq_lAeGEks5tOxmxgaJpZM4RmdZb .

rahulmutt commented 6 years ago

I think this discussion should also include how we're going to deal with lenses. Lenses are like the jQuery for pure FP languages and as such, I think it's rather unfortunate that they don't have support by default in the compiler (via deriving Lens) and the standard library (core lens type synonyms).

I've been going through the internals of the lens package in my spare time to see if we can extract a simple core that is powerful enough to handle 90% of the use cases, but lens-family may suffice. This may warrant another proposal in itself since there are many things to consider.

NickSeagull commented 6 years ago

Definitely, they are really useful

NickSeagull commented 6 years ago

I'm still haven't written the proposal because I'm still thinking a bit about what to write and what not. Today I stumbled upon into RIO, a Prelude that @snoyberg is trying to make the standard of Haskell, looks really great and I'm sure that we can borrow some ideas/build on top of that/use it.

rahulmutt commented 6 years ago

Sure, sounds good. RIO looks interesting but it looks like it has a lot of dependencies. I wonder if it's better to keep the prelude minimal and provide a list of libraries that should be used for standard use cases (i.e. immutable data structures via containers, etc). Thoughts on this?

NickSeagull commented 6 years ago

One of the things that I like about, say Java, is that you can import HashMap and use it directly when you need it. No need to add a library to your project, RIO dependencies', which by the way are these (so other readers dont have to go to RIOs repo)

dependencies:
- base >= 4.9 && < 10
- bytestring
- containers
- deepseq
- directory
- exceptions
- filepath
- hashable
- microlens
- mtl
- primitive
- text
- time
- typed-process >= 0.2.1.0
- unliftio >= 0.2.4.0
- unordered-containers
- vector

Seem reasonable to me. I would even probably throw async in. As all of those are used nearly in 90% of the projects (in my opinion) :smile:

rahulmutt commented 6 years ago

So to be clear, the discussion is about a prelude for Eta, which is a module whose functions/types are imported into all Eta programs without even using an import statement. When comparing with Java, that's equivalent to java.lang.* which is quite tiny.

What you're suggesting is that we have an eta-base with all those dependencies - that's a separate discussion altogether and I agree with what you've stated above like including standard data structures and such.

rahulmutt commented 6 years ago

@NickSeagull I've filed #657 to discuss the standard library itself.

rahulmutt commented 6 years ago

So the prelude for RIO is this file, which is what we should be comparing against when discussing about the prelude: https://github.com/commercialhaskell/rio/blob/master/src/RIO.hs

NickSeagull commented 6 years ago

Yep @rahulmutt , I completely messed up :sweat_smile: . Makes much more sense to have an eta-base :)

NickSeagull commented 6 years ago

I've just written the proposal: https://github.com/typelead/eta-proposals/blob/ccd129716a6267cc27c2ac7538d81c5ea17fce2c/proposals/EP002-NewPrelude.org . Let me know what you think!

NickSeagull commented 6 years ago

I was thinking about the type class functions aliases, wouldn't be weird to tell users that Functor is a type class that defines map, but then have them implement the fmap function in their instances?

I don't see how "for historical reasons" would be a good excuse :thinking:

On the other hand, it would be a great pain to have conversors between the Haskell typeclasses and the Eta ones. Maybe we should leave them as they are, not enforcing special names?

I don't know, here we have to choose between compatibility and good naming.

Another possibilty would be to say

Given that we are compatible with the Haskell ecosystem, the type classes are implemented in this way, but you should use the Eta standard functions.

Of course this is when someone asks why, probably we should not explain why things are like this. Elm has List.map, Maybe.map, etc... and nearly no one complains. When someone does, they just explain.

NickSeagull commented 6 years ago

Weird idea, how hard would be to extend the existing type classes in the existing Prelude?

Example:

class Functor f where
  fmap :: (a -> b) -> f a -> f b
  fmap = map

  map  :: (a -> b) -> f a -> f b
  map  = fmap

Do you think this would make sense?

rahulmutt commented 6 years ago

@NickSeagull I think we should leave the standard typeclasses alone. Create aliases? Sure, but mutating the definition might make things very messy.

Rather than thinking about hypotheticals, we can go ahead and make a new prelude and set up the build on the eta-prelude repo to compile against a good chunk of Hackage so that we can see the impact. To do that, we'll need to implement the -prelude flag (#308).

NickSeagull commented 6 years ago

Sure, after rethinking it for a while its a mess. But still, in my opinion its better to have a new prelude that enforces our style, and allow the user to do stuff like import Prelude (read) so Haskell modules are compiled with the regular prelude, and Eta ones, with the new one

NickSeagull commented 6 years ago

More ideas :rocket:

DSL sugar

For some functions, we might be interested in having DSL-style functions.

For example this code

data Order = First | Second | Third | Last

instance Enum Order
  fromEnum = ...
  toEnum = ...

main = traverse_ (enumFromTo First Last) print

could be rewritten as

data Order = First | Second | Third | Last

instance Enum Order
  fromEnum = ...
  toEnum = ...

main = foreach (from First to Last) print

where this DSL sugar is defined as:

data Keyword = To

to :: Keyword
to = To

from :: (Enum a) => a -> Keyword -> a -> [a]
from x To y = enumFromTo x y

A better Numerical Hierarchy

Quite a lot of people in dataHaskell have discussed about the possibility of adoption of NumHask as their prelude.

This is because it has a much better structure for numerical computing. It is described as:

In summary, the library doesn't do anything fancy. But if having to define (*) when you just want a (+) offends your sensibilities, it may bring some sanity.

Some examples:

>>> 1 / 1
1.0

The literal numbers in the divide above defaulted to Float rather than Int.

>>> 1 / (1::Int)
...
... No instance for (MultiplicativeGroup Int)
...

>>> 1 / fromIntegral (1::Int)
1.0

'Float' and 'Double' are 'NumHask.Algebra.Fields.Field' instances.

>>> zero == 0.0
True

'QuotientField'

>>> 1 `div` 2
0
>>> 3 `mod` 2
1

Basically it defines a bunch of different classes based on mathematical concepts that could be really useful for data science/analysis/engineering, having the Java interop, we could easily integrate with Apache Spark, DL4J, etc...

image

Also, it defines a lot of laws to be tested using QuickCheck:

image

NickSeagull commented 6 years ago

Dismiss the comment about NumHask, I've discussed it with the author and he sees it as completely non-plausible

rahulmutt commented 6 years ago

@Jyothsnasrinivas and I have discussed that we want to see a good ecosystem of data science/ML libraries for Eta, so we'd be happy to see any efforts in the Prelude that can help facilitate what you mentioned above with NumHask.

@tonyday567 can you comment here on why you think it's non-plausible so that we have your argument for reference?

As much as I love abstract algebra, my only reservations on the new hierachy is that it brings even more abstract mathematical jargon into the picture. If you can outline the benefit of the new hierachy such that it outweighs the cost, I have no issues. We can find a way to explain it nicely with diagrams/cheatsheets to make it easier for beginners.

tonyday567 commented 6 years ago

Nick misunderstood me - It's entirely plausible. At the moment, the library is very integrated with protolude, which I consider to be the top shelf haskell prelude for beginners - mostly because it removes String, and replaces partial functions. The next release of numhask, however, splits the library into numhask and numhask-prelude so that this dependency is removed from the core library.

numhask doesn't do much - pretty much just a bunch of class instances for the algebra we learn from middle school (it could almost be called arithmetic!). My very, very short pitch is that we are all so very familiar with +, -, and / that it makes sense to separate their classes as there are things that can + but not . The arithmetic operators are so ingrained in peoples minds they should be the go to API for things that have laws like association, distribution and such.

NickSeagull commented 6 years ago

Oh, sounds awesome!! Sorry for my misunderstanding :sweat_smile:

rahulmutt commented 6 years ago

@tonyday567 Thanks for the input! @NickSeagull You can go ahead and include discussion about numhask inspired typeclass hierarchy for the Eta prelude. @tonyday567 If you have any other suggestions in terms of how we go about implementing it, feel free to chime in.

NickSeagull commented 6 years ago

For now, I've finished a preliminary version of the Prelude. Feel free to criticize.

I think it is ready to be included, if stuff is missing we can just keep updating the Prelude :smile:

https://eta-lang.github.io/eta-prelude/index.html

jneira commented 6 years ago

I've take a look to Protolude and it looks very nice. Concretely i think we can consider include in eta prelude those design points:

Some of the other features (foldable/traversable) would be got if we upgrade base.

@NickSeagull the actual proposal is great overall. However i'd like to make some comments:

rahulmutt commented 6 years ago

@jneira As for operators, we won't know whether they are effective until we try them out. I feel that if we put them in a separate module in base, they will never really be used unless we use them throughout all the main docs.

The Arrow abstraction is rarely used and I think is considered obselete at this point. so I'm not too worried about that. In practice, I have seen (and myself used) the first and second methods and sometimes &&& and ***, but the others are fairly obscure.

I don't think we need to tune the Prelude for Haskell users - Haskell users already will have their own personal prelude of choice and will continue to use that for their projects. The beginners from other more widely used FP languages (Scala, Clojure, F#, Elixir, etc.) are the audience for the prelude.

@NickSeagull Your thoughts on this?

jneira commented 6 years ago

Well, if eta continues supporting the haskell prelude for .hs files as default, users can use them if they want, so we can have both options avaliable (and optionally use in each one the default prelude of the other). In fact head:: [a] -> Maybe a will break most of examples and tutorials out there so breaking things can be fine.

Re <+>, Apart of the clash with the arrow operator, i keep thinking the additional + can feel a little strange (dont know if there are more cases but Product....)

rahulmutt commented 6 years ago

Yes, .hs files will still use the Haskell Prelude by default, otherwise we will have massive breakage of eta-hackage! The prelude we're discussing now will only be automatic for files with the .eta extension.

I agree that <+> can feel a bit strange. I think @NickSeagull was trying to make it look like this. The + will bias the reader towards addition and as you said, and Product-like monoids will look weird. I missed this point in your earlier post.<>, on the other hand is not biased towards a particular mathematical operation.

NickSeagull commented 6 years ago

Yep, my intention was to make it look like the one that you linked @rahulmutt .

Still, I think that our brains have the monoids wired through addition and it will help newcomers to understand the concept easily.

chshersh commented 5 years ago

Another source where you can take inspiration is relude:

You can find the short overview of relude here:

In some sense relude closer to protolude than to RIO but has a lot of differences from protolude. The idea is to be minimal but still useful. One key point there is custom HLint rules. I think if you develop custom prelude which is different from the standard Haskell prelude, it's a good idea to include HLint rules to help beginners to use your prelude as well. The nice thing about relude is that HLint rules are generated via Dhall. I see some discussion around Dhall and Eta in #704. If you can specify .cabal files with Dhall, the custom HLint can take some fields from the package configuration (like default-extensions).

NickSeagull commented 5 years ago

Sure @chshersh, relude is great!

By the way, what should be the next steps to push this forward? I'm really willing to help with this so we have a sane prelude

rahulmutt commented 5 years ago

@ChShersh Really awesome suggestions. I like the idea of specifying hlint rules in the cabal file or maybe even in an external file which you can specify in the cabal file. Will need to think over the long term consequences and make think more about the UX. Perhaps you'd like to make a proposal?

@NickSeagull

1) Make a -prelude flag that you can send into the compiler which takes a module name as an argument. The default value will be Prelude (the Haskell prelude) and as we move forward we can change the default to your work on eta-prelude.

2) Make a new field in the cabal file called prelude which lets the user specify the prelude for a given component of their project which will send the -prelude flag to the compiler from (1).

Happy to provide assistance as needed. The changes should (hopefully) be minor.

puffnfresh commented 5 years ago

My preferred alternative prelude is https://github.com/qfpl/papa - I think a -prelude flag would be pretty useful for libraries like it.