Skip to main content

· One min read
Nick Tchayka
STATUS - DRAFT

This NHEP will contain the index of all NeoHaskell Enhancement Proposals, known as NHEPs. NHEP numbers are assigned by the NHEP editors, and once assigned are never changed. The Git history of the NHEP texts represent their historical record.

In the future, the NHEPs will be listed here by category. For now, use the sidebar on the left.

A good starting point is NHEP 1, which describes the NHEP process itself.

· 2 min read
Nick Tchayka
STATUS - IN PROGRESS

What is a NHEP?

NHEP stands for NeoHaskell Evolution Proposal. It is a document that describes a change or addition to NeoHaskell.

Statuses of a NHEP

A NHEP can have one of the following statuses:

STATUS - DRAFT

This status indicates that the NHEP is still being written and is not ready for review.

STATUS - IN PROGRESS

This status indicates that the NHEP has been accepted but is still being implemented.

STATUS - INTEGRATED

This status indicates that the NHEP has been implemented.

STATUS - ACCEPTED

This status is for informational NHEPs that have been accepted.

STATUS - REJECTED

This status indicates that the NHEP has been rejected.

How to contribute to the design process

Everyone is welcome to propose, discuss, and review ideas to improve NeoHaskell in the #proposals channel of the Discord server.

Note that the project is in a very early stage, and the contribution to the design process is not well defined.

As some general rules for now, take this into account before submitting a proposal:

  • No "What about if NeoHaskell does a 180-degree turn and instead does this completely unrelated thing?". These kinds of proposals are seen as completely out of the scope of the NeoHaskell project and will be instantly dismissed.
  • Use constructive criticism. Instead of "remove this, I don't like it", take a moment to think and give actual reasons like "I believe that this function in the standard library is not clear enough, someone could understand this in a wrong way"
  • Is the problem being addressed impactful enough to warrant a change to NeoHaskell?
  • How does this impact the Principle of Least Astonishment of the project?
  • How does this impact the Principle of Developer Happiness of the project?
  • How does this impact the Principle of Least Effort of the project? Both externally for the users and internally for the maintainers.
  • Does this proposal fit well with the feel and direction of NeoHaskell?
  • What other libraries/languages got you inspired to submit this proposal? How does this compare to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Please state explicitly whether you believe that the proposal should be accepted into NeoHaskell.

· 5 min read
Nick Tchayka
STATUS - ACCEPTED

Introduction

This document defines the target audience and user persona for the NeoHaskell project.

The definition of this target audience and user persona is intended to guide the design and development of the NeoHaskell project. It is not intended to be a strict definition of who can or cannot use the NeoHaskell project, only a guide to help the project team make decisions. Everyone is welcome to use NeoHaskell, regardless of whether they fit the target audience or user persona.

If a user doesn't fit the target audience that doesn't mean that they shouldn't use NeoHaskell, it just means that the design process and showcasing process won't be prioritizing them.

Target Audience

The target audience of NeoHaskell are developers who have a maker/hacker philosophy, who are comfortable with a statically typed mainstream language like TypeScript or Java, and who are interested in developing a project as fast as possible to test an idea or prototype a product, even if they aren't the most experienced software developers. They are not interested in spending a lot of time learning a new programming language in-depth, they are not interested in learning software architecture in-depth, and they are not interested in spending a lot of time learning a new framework.

They are interested in learning new things, as long as those are steps in the direction of their goal. They are willing to learn a new programming language, as long as they can start writing code in a few hours. They are willing to learn a new framework, as long as they can start using it in a few hours. They are willing to learn a new architecture, as long as they can start using it in a few hours.

These are not interested in having a perfect code quality from the beginning, so they won't be investing time in thoroughly testing their code, regardless of the testing methodology. And although they do not know it, they value working in a domain-driven way.

They will probably be interested in learning more about the programming language, the framework, and the architecture, but only after they have a working prototype of their project.

They don't have enough time to spend on their projects, probably because they have a full-time job, or they are students, or they have a family, or they have other hobbies. So they prefer a command line tool that does everything for them, instead of having to research the different tools for their technology stack, and then having to configure them to work together.

Who's not the target audience

Well-seasoned developers who have experience either in Haskell or in functional programming in general, and who are looking to apply concepts well-established in the functional world.

Users who are interested in type theory, category theory, programming language theory, and other advanced topics in computer science.

Again, if a user doesn't fit the target audience doesn't mean that they shouldn't use NeoHaskell, it just means that the design process and showcasing process won't be prioritizing them.

User Persona

  • Name: Jess
  • Role: Junior Software Developer

Actions, Motivations, and Pains

  • What do I do?
    • Work full-time as a software developer in a TypeScript software shop.
    • In the evenings and weekends I try to build up my portfolio by working on side projects.
    • Learning good practices, patterns, and technologies that act as leverage for my learning process.
  • Why do I do it?
    • I want to improve my skills as a software developer.
    • Having a good portfolio will help me get a better job.
    • One of my projects might become a startup.
  • What do I want?
    • I want to build a project as fast as possible.
    • I want to have negotiation power by mentioning skills that are relevant in the market, regardless of the technology stack.
    • I want to have fun while learning.
  • What's stopping me?
    • There are a lot of things to learn, and I don't know where to start.
    • Many times, existing tools have so many pitfalls that I end up spending more time fixing them than actually working on my project, which is frustrating.
    • I don't have enough experience to reason too much about all the side-effects that are happening under the hoods, that make my code behave in unexpected ways. This leads to a lot of frustration, and I end up thinking that I'm not good enough to be a software developer.

Values

  • What convinces me?
    • Having good documentation that explains the concepts that I need to know.
    • Having recipes and precise instructions on the steps I need to follow to achieve my goals.
    • Being able to understand the code that I'm looking at.
    • Success stories of other people of my skill level who have achieved similar goals as mine.
  • What or who informs me?
    • Dev.to
    • Friends and colleagues
    • Twitter

Context

  • Where am I?
    • At home
    • At a coffee shop
    • On public transportation
  • What's my day to day?
    • One hour commute to and from work
    • Attend daily standup meetings
    • Work on my assigned tasks
    • Have to do groceries and other chores after work
    • Spend time with my family
    • If there's time left, work on my side projects

Conclusion

Here we describe the target audience and user persona for NeoHaskell. All the design and development decisions will be made with this target audience and user persona in mind.

We will try to make the project as accessible as possible for these kinds of users. So they can enjoy even the last 15 minutes of the day, where they have time to work on their side projects.

· 5 min read
Nick Tchayka
STATUS - ACCEPTED

Introduction

This document defines the design principles that guide the design and implementation processes of NeoHaskell. They are intended to guide the decisions in both the high-level, as well as the low-level, decision-making process of the project.

Their purpose is to ensure that the project prioritizes the correct aspects of the design and implementation, and that the target audience and user persona are kept in mind at all times, as they are the most important stakeholders of the project.

Principle of Least Astonishment

The Principle of Least Astonishment, also known as the Principle of Least Surprise, is a guideline in user interface and software design that emphasizes creating systems that behave in a manner consistent with user expectations, thereby minimizing user surprise and confusion. The principle advises that system components should act in alignment with widely accepted conventions and their syntactical indications. It encourages designers to ensure that the behavior of a system or feature closely matches users' existing knowledge and expectations to reduce the learning curve and prevent user astonishment. Although the principle originated in the late 1960s, it remains relevant in different technological platforms and settings to ensure that functionalities such as keyboard shortcuts, widgets, or method names are intuitive and adhere to familiar patterns for users, thereby enhancing user experience and system usability.

Examples

Those might not look like much, but they are details that end up stacking and streamlining the overall experience. The aim is to reduce the cognitive load on the user, and they are already learning a new language and ecosystem, so we should remove as much stuff to learn as possible. For example, promoting:

  • JSON or YAML as configuration formats instead of TOML or Dhall.
  • Git as the version control software instead of Darcs or Pijul.
  • GitHub as the code hosting platform instead of GitLab or BitBucket.
  • Visual Studio Code as the IDE instead of Emacs or Vim.

Principle of Developer Happiness

The Principle of Developer Happiness prioritizes establishing an environment that aligns with software developers' professional and personal expectations, thereby enhancing satisfaction and retention. It underscores creating engaging experiences and a supportive culture where developers are fairly compensated, and find alignment with the group's mission. Additionally, it advocates for the use of efficient tools and technologies to streamline the development process and save time. Finally, it emphasizes continuous visibility into and assessment of developer efficiency through surveys and feedback, ensuring a continually optimized working environment that resonates with developer needs and expectations. This principle aims to meld operational strategies with developer satisfaction, ensuring an atmosphere that intuitively supports developers.

Examples

This principle emphasizes creating an environment that aligns with the expectations of both developers and users of NeoHaskell. It centers around fostering satisfaction, engagement, and a supportive community. Here are some examples:

  • Clear Documentation: Provide user-friendly and comprehensive documentation with code examples.
  • Community Engagement: Encourage active participation and collaboration within the open-source community.
  • Transparent Decision-Making: Maintain transparency in language development decisions.
  • Inclusive Onboarding: Offer resources for developers of all skill levels.
  • Recognition: Acknowledge and appreciate contributions and projects from the community.
  • Feedback Channels: Create accessible channels for users and contributors to provide input and report issues.

Principle of Least Effort

The Principle of Least Effort underscores the axiom that entities, within a given context, will opt for the solution that requires the minimal amount of work or complexity to achieve a specific goal. Applied within the realm of software design and development, it emphasizes creating systems and interfaces that are straightforward, easy to comprehend, and simple to interact with, thereby reducing the cognitive and operational load on both the user and developer. On the user's side, interfaces should be intuitive, providing the easiest pathway to accomplish tasks with minimum steps and complexity. For developers, this principle encourages the creation of code and architectures that are clean, succinct, and straightforward to understand and modify. Ultimately, adhering to the Principle of Least Effort enables the creation of more user-friendly applications and sustainably maintainable codebases, promoting efficient and effective interactions for all involved parties.

Examples

  • Simple Syntax: Design the language with a straightforward and intuitive syntax to reduce the cognitive load on developers using the language.
  • Clear and Concise Documentation: Provide comprehensive yet concise documentation to help users understand and utilize the language effectively without unnecessary complexity.
  • Standard Libraries: Include a well-organized standard library that simplifies common programming tasks, reducing the effort required to implement them from scratch.
  • Error Handling: Implement user-friendly error messages and handling mechanisms that assist developers in identifying and resolving issues efficiently.
  • Community Support: Foster a supportive community where developers can seek help, share knowledge, and collaborate, reducing the effort needed to overcome challenges.
  • Version Management: Streamline version management and updates to make it easy for users to adopt new language features and improvements.

Conclusion

In conclusion, the design principles outlined for NeoHaskell serve as a fundamental compass for the project's development and implementation processes. The Principle of Least Astonishment underscores the importance of aligning system behavior with user expectations, enhancing user experience and usability. The Principle of Developer Happiness focuses on creating an environment that satisfies both developers and users, emphasizing engagement, transparency, and continuous improvement. Lastly, the Principle of Least Effort promotes simplicity and efficiency, reducing the cognitive and operational load on users and developers alike.

These principles collectively ensure that NeoHaskell remains user-centric, developer-friendly, and efficient throughout its development journey. By adhering to these guiding principles, NeoHaskell aims to not only meet but exceed the expectations of its target audience and user persona, ultimately leading to a successful and user-satisfying language.

· 3 min read
Nick Tchayka
STATUS - ACCEPTED

Introduction

In the context of the NeoHaskell ecosystem, we employ the Semantic Versioning (SemVer) schema. SemVer consists of a set of rules and guidelines for assigning and incrementing version numbers within our software development process. By embracing SemVer, NeoHaskell addresses the intricacies of managing dependencies, especially as the ecosystem complexity evolves. The primary objective of utilizing Semantic Versioning within the NeoHaskell ecosystem is to ensure that our version numbers are both transparent and informative, effectively communicating the nature of changes made to our software.

Impact on Principle of Least Astonishment

Semantic Versioning has a positive impact on the Principle of Least Astonishment. First of all, it is a very popular schema, used by many open-source projects. Second, adhering to clear and consistent rules for version numbering reduces confusion and surprises for developers and users of software packages. When version numbers follow a predictable pattern (Major.Minor.Patch), it becomes easier for stakeholders to understand the significance of each release. This predictability enhances the user experience and makes it easier to manage software dependencies.

Impact on Principle of Developer Happiness

Semantic Versioning contributes to the Principle of Developer Happiness. It provides developers with a systematic approach to versioning, which simplifies the process of releasing and upgrading software packages. Developers can confidently make changes to their codebase while following SemVer guidelines, knowing that version numbers convey the impact of those changes. This reduces the stress associated with managing dependencies and allows developers to focus on building and improving their software.

Impact on Principle of Least Effort

Semantic Versioning aligns with the Principle of Least Effort by streamlining the management of software dependencies. When developers adhere to SemVer, they can make backward-compatible changes without having to release new major versions. This reduces the effort required to maintain and update software packages. Additionally, clear documentation of public APIs and the use of version numbers to indicate compatibility further simplify the integration of dependencies, minimizing the effort needed to ensure smooth interactions between software components.

Usage of SemVer in early development phases

During the early development of the NeoHaskell project, the major version will be kept at 0, so that the community can expect breaking changes to occur frequently. Once the project reaches a stable state, the major version will be incremented to 1, indicating that breaking changes will be made less frequently.

Standardizing the use of SemVer

The Semantic Versioning schema is advised as a standard for all NeoHaskell projects, as it will help all of the ecosystem to be in sync.

The neo CLI tool will easily help bumping versions, as it will inspect the exported functions and types and will suggest the next version based on the changes made.

Considerations

Note that even though the API of a package is not changed, the implementation of a function can be changed, which can lead to different results. This is considered a breaking change and should be reflected in the version number.

Take a look at this GitHub thread for examples and more information.

· 2 min read
SiriusStarr
STATUS - DRAFT

Introduction

By default, Haskell's String type is only an alias for [Char] and as such is inefficient for non-trivial uses (e.g. due to slow appends). The text package provides a more sensible Text type and has become essentially the standard within the Haskell ecosystem.

NeoHaskell should use Text as its default string type under the more familiar name String and support literals via the standard "double quoted" syntax.

Impact on Principle of Least Astonishment

The fact that a simple string literal is inefficient and usually undesirable violates the Principle of Least Astonishment. Most new programmers expect String and "string literals" to work as expected without any additional effort. This change will align NeoHaskell with those expectations.

Impact on Principle of Developer Happiness

As noted below, this change will require less boilerplate of developers and allow them to get to actual code faster. Eliminating the use of OverloadedStrings will also cut down on unexpected type errors.

Impact on Principle of Least Effort

Currently, nearly every Haskell project requires a dependency on text and files are littered with import Data.Texts and {-# LANGUAGE OverloadedStrings #-}s. This change will eliminate all of that overhead and allow the use of a sensible string type with better ergonomics.

Implementation

In early stages, this can be achieved via prelude and pragmas by newtypeing Text to String and use of OverloadedStrings with:

default IsString String -- where String is the new type over Text

Considerations

Since Text is the standard for most of the Haskell ecosystem, this change is unlikely to have many negative ramifications, other than possible confusion over the String type in NeoHaskell actually being Text and not String, but this is only of concern for developers stepping outside of the NeoHaskell ecosystem.

Another consideration is the existence of the other common data type ByteString. Text is more often the sensible default for all except extremely performance-critical applications, so this should not be a problem.

· 2 min read
SiriusStarr
STATUS - DRAFT

Introduction

By default, Haskell's List type is a simple linked list and as such is inefficient for many common operations (getting by index, appending, iterating from the end, etc.). A number of other more efficient list-like data structures are available, most notably the array-like Vector and the list-like Seq.

NeoHaskell should choose one of these two data structures as its default list type and support literals via the standard [1, 2, 3] syntax.

Impact on Principle of Least Astonishment

The fact that a simple list literal is inefficient to append or get by index and usually undesirable violates the Principle of Least Astonishment. Most new programmers expect sytactic sugar like [1, 2, 3]" to produce a commonly desirable data type without any additional effort. This change will align NeoHaskell with those expectations.

Impact on Principle of Developer Happiness

As noted below, this change will require less boilerplate of developers and allow them to get to actual code faster, as well as gently guiding developers to choose a data type without them having to decide which to use.

Impact on Principle of Least Effort

Currently, nearly every Haskell project requires a dependency on containers and/or vector and files are littered with imports and {-# LANGUAGE OverloadedList #-}s. This change will eliminate all of that overhead and allow the use of a sensible list type with better ergonomics.

Implementation

In early stages, this can be achieved via prelude and pragmas by the use of OverloadedLists with:

default IsList Vector -- or Seq

Considerations

Haskell's default lists are more efficient for LIFO queues than either of these data types. There could, however, be a Queue data type or the like to push developers in that when it's desirable.

List comprehensions (if they will be in NeoHaskell) would need to support this data type as well.

Vector is not pattern-matchable on. Seq does not have this issue, which might be a point in favor of it.

· 2 min read
SiriusStarr
STATUS - DRAFT

Introduction

Haskell uses :: for type signatures (and the shorter : for cons). Type signatures are generally far, far more numerous than cons operations/pattern matches, and so most Haskell-descended languages have chosen to use : for type signatures instead, for the sake of cleaner-looking code and fewer developer keypresses. : for cons does highlight the importance of lists in the original conception of Haskell, but with the default list type possibly changing (per NHEP 6), it will likely be de-emphasized in NeoHaskell.

NeoHaskell should use a single colon : to denote type signatures instead.

Impact on Principle of Least Astonishment

Most languages descended from Haskell have made this change, including Elm, Idris, and Agda. Additionally, many unrelated languages use the same syntax for types, including F#, Dhall, and OCaml, or similar syntax featuring a single colon, e.g. TypeScript, Elixir, Rust, and Python (for type hints).

Almost no other language except Purescript uses :: while all of the aforementioned use : in some capacity, so this change will make NeoHaskell's syntax more intuitive to non-Haskell developers.

Impact on Principle of Developer Happiness

Since there are far, far more type signatures than cons in most Haskell code, this change renders syntax visually cleaner, more familiar, and reduces the number of required keypresses.

Impact on Principle of Least Effort

As mentioned above, this change reduces the number of required keypresses to write a type signature.

Considerations

This will require a change to the cons operator of lists. :: is of course available, though that could perhaps be used for a new default list type instead per NHEP 6. Regardless, this decision must be made as well.

This change will render NeoHaskell slightly less familiar to Haskell developers. However, as noted above, most other languages descended from Haskell have made the same change, and it is unlikely to cause significant friction.

· 6 min read
Nick Tchayka
STATUS - DRAFT

Introduction

Haskell doesn't allow writing code directly in a file, with the intent of running it as a script. Similarly to Java, Rust or C, it forces the user to put the code that is going to be run in a main function.

Contrary to Java or the others, the syntax is extremely useful for scripts, as it is expressive and concise. There are solutions like runhaskell that allow you to run a Haskell file that defines a main function, and users are already using it for scripts. For example, the widely used library ghc-lib uses it for their CI/CD processes.

Possible Implementation

Imports would be parsed and put at the top level, the rest would be inside of a main function. The users would have to run the module's main function explicitly, so there are no unexpected side effects on imports.

Example with no exports

module MyScript where

import File

foo <- File.read "foo.txt"

myFunction :: Int -> String
myFunction n =
Int.toString n + foo

This would be compiled to:

module MyScript where

import File

main = do
foo <- File.read "foo.txt"

myFunction :: Int -> String
myFunction n =
Int.toString n + foo

pass -- This is a function that does nothing but is required to make the main function valid

Example with exports

The complexity is exporting symbols. If one would want to export some kind of function or data type from this kind of module, those would have to be put at the top level, which means that they wouldn't be allowed to use stuff from the scope they're defined with. E.g.

module MyScript (myFunction) where

import File

foo <- File.read "foo.txt"

myFunction :: Int -> String
myFunction n =
Int.toString n + foo

This would be compiled to:

module MyScript (myFunction, main) where

import File

myFunction :: Int -> String
myFunction n =
Int.toString n + foo -- Woops, foo is not defined!

main = do
foo <- File.read "foo.txt"
pass

A solution would be to have a compile-time marker that marks the function as reexported in a script-style module, and if it has a <symbol> is not in scope error, tell that it is not allowed to do that kind of stuff because it is introducing side effects, and we would direct the user to the proper documentation that explains why not.

Another solution, which is a double-edged blade, is to do some symbol tracking and if the function has to be reexported to the top level, we create a top-level mutable reference that:

  1. Is NOT reexported
  2. Is initialized in main
  3. The function does an unsafe read of the mutable reference, using that value.

This has the problem that if main hasn't been called, this would fail in runtime.

A naive solution would be to introduce a runtime check, but that would make the code slower, and we'd introduce a runtime error that would be hard to debug, which goes against the principles of NeoHaskell.

A possibility would be to have another compile-time check, that would disallow the usage of the function if the main function of that module hasn't been called yet. Still, that is a complex task to implement, as we'd have to figure out a way to track that, especially when branching is introduced, or even when concurrency is at play.

Impact on Principle of Least Astonishment

Most languages nowadays allow writing scripts directly in a file, so it would not be surprising for users to be able to do that in NeoHaskell.

On the other hand, having the separation between the import and the module initialization is something not common at all in other languages, so it is a very alien thing unless we manage to find a way to make it more familiar.

A possibility here could be that script-style modules are not allowed to export anything, as they are scripts, meant to be run directly, and not imported.

Also, another possibility would be to have main to return a record of functions, as scripts do not return anything. (They can print stuff, and write to files, but they do not return anything, apart from an exit code).

This could introduce a powerful tool for defining services, as the service initialization would be done in the main function and the service would be returned as a record of functions, which would be the API of the service:

module ProductService (
count,
deleteById
) where

import File
import Env

dbFile <- Env.get "DB_FILE"
|> orElse "db.sqlite"

tableName <- Env.get "TABLE_NAME"
|> orElse "products"

db <- File.read dbFile

count :: IO Int
count = ...

deleteById :: UUID -> IO
deleteById id = ...

If we wrapped everything into a function named init making it compile to the following, it would enable a very interesting pattern for defining services:

module ProductService (
count,
deleteById
) where

data ProductService = ProductService {
count :: IO Int,
deleteById :: UUID -> IO
}

init :: IO ProductService
init = do
dbFile <- Env.get "DB_FILE"
|> orElse "db.sqlite"

tableName <- Env.get "TABLE_NAME"
|> orElse "products"

db <- File.read dbFile

count :: IO Int
count = ...

deleteById :: UUID -> IO
deleteById id = ...

yield ProductService {
count,
deleteById,
}

So now, some code that would use this service could do the following:

import ProductService

main = do
product <- ProductService.init
totalProducts <- product.count
print (Int.toString totalProducts)

Perhaps, at this point, it'd make sense to introduce a new keyword for defining services, instead of relying on module? Maybe service?

service ProductService (
count,
deleteById
) where

-- and so on...

Impact on Principle of Developer Happiness

Many technologies allow the use of the same language as a way of configuring the application/project, for example, in Ruby they use the language itself to define project dependencies or even the specification of the project. Here's the gemspec file of Ruby on Rails.

On the other hand, instead of writing scripts with Bash, Python or Ruby, one could use NeoHaskell, which is a much more powerful language, and it would allow the user to use the same language for everything. Nowadays, people are using Python in an "explorative" manner, where they write scripts and keep tweaking them until they get the desired result, and then they put that into their application.

Impact on Principle of Least Effort

This removes the necessity of having to write a main function, which is a very small thing, but it is still something that the user has to do, and it is not something that is needed in other languages.

On the other hand, this does introduce many implementation tasks in the NeoHaskell codebase, so it is not something that can be done in a short amount of time.