Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: The Eta Prelude #617

Open
NickSeagull opened this issue Jan 22, 2018 · 42 comments
Open

RFC: The Eta Prelude #617

NickSeagull opened this issue Jan 22, 2018 · 42 comments

Comments

@NickSeagull
Copy link
Collaborator

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:

  • Substitute $ by <|
  • Create new operator |> that is flip ($)
  • Default function composition with <<< and the flipped with >>> (Comes from Control.Arrow)
  • Substitute <$> by <$| and create |$> for flip (<$>)
  • Substitute <*> by <*| and create |*> for flip (<*>)

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 😄

@rahulmutt
Copy link
Member

@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
Copy link
Collaborator Author

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

@puffnfresh
Copy link
Collaborator

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
Copy link

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
Copy link
Collaborator

jneira commented Jan 23, 2018

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
Copy link
Collaborator Author

NickSeagull commented Jan 23, 2018

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

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

@NickSeagull
Copy link
Collaborator Author

@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
Copy link
Member

@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
Copy link
Collaborator

Does a library like this already exist?

@tonymorris
Copy link

@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
Copy link
Collaborator Author

NickSeagull commented Jan 27, 2018

@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
Copy link

tonymorris commented Jan 27, 2018 via email

@rahulmutt
Copy link
Member

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
Copy link
Collaborator Author

Definitely, they are really useful

@NickSeagull
Copy link
Collaborator Author

NickSeagull commented Feb 15, 2018

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
Copy link
Member

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
Copy link
Collaborator Author

NickSeagull commented Feb 15, 2018

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) 😄

@rahulmutt
Copy link
Member

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
Copy link
Member

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

@rahulmutt
Copy link
Member

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
Copy link
Collaborator Author

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

@NickSeagull
Copy link
Collaborator Author

NickSeagull commented Mar 12, 2018

@NickSeagull
Copy link
Collaborator Author

NickSeagull commented Mar 28, 2018

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 🤔

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
Copy link
Collaborator Author

NickSeagull commented Mar 28, 2018

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
Copy link
Member

@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
Copy link
Collaborator Author

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
Copy link
Collaborator Author

More ideas 🚀

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:

  • Be as compatible as practical with the existing haskell ecosystem. Ints, Integers, Floats, Doubles and Complex are taken from base and given numhask instances, so they are also Num instances. Monoid and Semigroup are not used in numhask to maintain compatability.
  • Be a replacement for anything in base that has a Num, Fractional or Real constraint.
  • Include QuickCheck tests of the numeric laws implicit in the classes. This also includes tracking where laws are approximate or fail for non-exact numbers.
  • Have the usual operators (+) and (*) operators be reserved for commutative relationships, with plus and times being used for non-commutative ones.

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
Copy link
Collaborator Author

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

@rahulmutt
Copy link
Member

rahulmutt commented Mar 31, 2018

@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
Copy link

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
Copy link
Collaborator Author

Oh, sounds awesome!! Sorry for my misunderstanding 😅

@rahulmutt
Copy link
Member

@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
Copy link
Collaborator Author

NickSeagull commented Apr 2, 2018

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 😄

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

@jneira
Copy link
Collaborator

jneira commented Apr 7, 2018

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:

  • Unsafe functions are prefixed with "unsafe" in a separate module.
  • StateT/ReaderT/ExceptT transformers in scope by default.
  • Compiler warning on bottoms.
  • Types for common data structures in scope.
  • Banishes impure exception throwing outside of IO.

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:

  • About "pipes" operators i'd say that i see the appealing of the style of the code produced with them, however
    • Imo those operators looks too similar and they represent abstractions very different.
    • Not sure if build/understand medium/large chains with them can be easy for beginners (like point free?)
    • As alternative we could add a module in base with those operators and later, if they are widely used, add them to Prelude
  • The <+> operator for mappend can look a bit strange for some instances:
    • Not sure if it worths break the modules using the same operator from Arrow
>>> getProduct (Product 3 <+> Product 4 <+> mempty)
12
  • Maybe we should keep the actual regular haskell function names (fst, snd,...), to help users coming form haskell or trying to learn using haskell resources.

@rahulmutt
Copy link
Member

@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
Copy link
Collaborator

jneira commented Apr 9, 2018

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
Copy link
Member

rahulmutt commented Apr 9, 2018

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
Copy link
Collaborator Author

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
Copy link

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
Copy link
Collaborator Author

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
Copy link
Member

@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
Copy link
Collaborator

puffnfresh commented Oct 18, 2018

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants