Skip to main content

· 4 min read
Matija Sosic

Welcome Shayne!

Find Shayne on LinkedIn and GitHub.

Wasp is a configuration language (DSL) for building full-stack web apps with less code and best practices that works alongside React and Node.js. We are on a mission to streamline web app development while empowering developers to continue using the power of code and their favorite tools. We are backed by Y Combinator and engineers from Airbnb, Facebook, and Lyft.

We are in Alpha (try it out)!Join our communityWork with us

We are super excited to introduce Shayne, the first person to join the Wasp team! Shayne is a battle-tested veteran engineer, with experiences ranging from leading teams at high-growth startups to working at enterprise giants such as Red Hat and NetApp. Along with that, he is super nice and incredibly pleasant to work with - we are beyond thrilled that he chose Wasp for his next adventure with him and can't wait for you to meet him in our Discord community!

Why did you join Wasp?#

I have always been excited about high-quality dev tooling and web frameworks, and I am also interested in Haskell/compilers. The technology, problem space, and team were just too compelling to pass up. I was also excited to be on the ground floor of a YC startup, where I can have a significant impact and help build a broad, welcoming, open-source community of Wasp developers.

What did you do before?#

I have been a professional developer for over a decade, mostly in backend web development, with experience from Lockheed Martin, Morgan Stanley, NetApp, and Red Hat. Most recently, I was the head of engineering at an edtech company called LearnPlatform, where we were handling a quarter of a billion incoming events per day with the goal of understanding and improving student access to technology that works best for them.

What is your favorite language/framework?#

My favorite framework is probably Ruby on Rails, for the elegance of ideas and seamless implementation. I never had an actual favorite programming language, as I enjoy different aspects of Ruby, Elixir, JavaScript, C#, and others. My least favorite has always been Java. My current favorite language is fast becoming Haskell. :)

The most interesting niche programming language I have used professionally was Ada at Lockheed Martin. We used it to build distributed, real-time, full-motion flight simulators for the military (think multi-million dollar, hyperrealistic multiplayer video games).

What are you most excited about in Wasp?#

As web developers, I think we have gotten accustomed to a certain level of complexity that is not associated with the problem we are solving but the boilerplate of the process. This lack of nuance between accidental and essential complexity has recently led to less than ideal low-code approaches. Wasp, in my view, takes the better approach of a higher-level DSL to abstract some of the typical details using best practices, leaving you to focus on your problem by writing actual code that produces a real web app without any vendor lock-in. That is pretty amazing to me!

How did you start coding?#

Probably by creating some basic LAMP apps in the late 90s while in high school. Growing up, our parents wanted us to have summer jobs to earn money we could spend during the rest of the year. I quickly found that freelance web development on Elance, and similar sites, was more enjoyable and profitable than the alternatives available to 15-year-olds. From then on, I was hooked.

What is your dev setup?#

MacBook Air M1 with an external Dell display, Magic Trackpad, and a split mechanical keyboard from UHK (Ultimate Hacking Keyboard).

camelCase or snake_case?#

I default to whatever the language or codebase conventions are. Visually, I prefer snake case, though (and definitely spaces over tabs). ;)

What's one piece of advice you'd give to an aspiring developer?#

One of the biggest differentiators I have found between good and great engineers is that the great ones possess a continuous desire to learn and grow. They view challenges as fun opportunities to expand their knowledge and skills, recognizing that they always have room for improvement. The corollary is that impostor syndrome is real and never goes away, so try not to be too hard on yourself along the way!

This post was the first of several new hire announcements in the months to come, so stay tuned and reach out if you want to work with Martin, Shayne, and myself!

We are in Alpha (try it out)!Join our communityWork with us

· 10 min read

Enter Waspello

Try Waspello here! | See the code

Wasp is a configuration language (DSL) for building full-stack web apps with less code and best practices that works alongside React and Node.js. We are on a mission to streamline web app development while empowering developers to continue using the power of code and their favorite tools. We are backed by Y Combinator and engineers from Airbnb, Facebook, and Lyft.

We are in Alpha (try it out)!Join our communityWork with us

Why Trello?#

While building Wasp, our goal is to use it as much as we can to build our projects and play with it, so we can learn what works and what we should do next. This is why Trello was a great choice of app to build with Wasp - it is one of the most well-known full-stack web apps, it's very simple and intuitive to use but also covers a good portion of features used by today's modern web apps.

So let's dig in and see and how it went - what works, what doesn't and, what's missing/coming next!

What works?#

It's alive ⚡🤖 !!#

The good news is all the basic functionality is here - Waspello users can signup/log in which brings them to their project board where they can perform CRUD operations on lists and cards - create them, edit them, move them around, etc. Let's see it in action:

Waspello in action

Waspello in action!

As you can see things work, but not everything is perfect (e.g. there is a delay when creating/moving a card) - we'll examine why is that so a bit later.

Under the hood 🚘 🔧#

Here is a simple visual overview of Waspello's code anatomy (which applies to every Wasp app):

Waspello code anatomy

Waspello code anatomy

Let's now dig in a bit deeper and shortly examine each of the concepts Wasp supports (page, query, entity, ...) and learn through code samples how to use it to implement Waspello.

Entities#

It all starts with a data model definition (called entity in Wasp), which is defined via Prisma Schema Language:

main.wasp | Defining entities via Prisma Schema Language
// Entities
entity User {=psl    id          Int     @id @default(autoincrement())    email       String  @unique    password    String    lists       List[]    cards       Card[]psl=}
entity List {=psl    id          Int     @id @default(autoincrement())    name        String    pos         Float
    // List has a single author.    user        User    @relation(fields: [userId], references: [id])    userId      Int
    cards       Card[]psl=}
entity Card {=psl    id          Int     @id @default(autoincrement())    title       String    pos         Float
    // Card belongs to a single list.    list        List    @relation(fields: [listId], references: [id])    listId      Int
    // Card has a single author.    author      User    @relation(fields: [authorId], references: [id])    authorId    Intpsl=}

Those three entities are all we need! Wasp uses Prisma to create a database schema underneath and allows the developer to query it through its generated SDK.

Queries and Actions (Operations)#

After we've defined our data models, the next step is to do something with them! We can read/create/update/delete an entity and that is what query and action mechanisms are for. Below follows an example from the Waspello code that demonstrates how it works.

The first step is to declare to Wasp there will be a query, point to the actual function containing the query logic, and state from which entities it will be reading information:

main.wasp | Declaration of a query in Wasp
query getListsAndCards {    // Points to the function which contains query logic.    fn: import { getListsAndCards } from "@ext/queries.js",
    // This query depends on List and Card entities.    // If any of them changes this query will get re-fetched (cache invalidation).    entities: [List, Card]}

The main point of this declaration is for Wasp to be aware of the query and thus be able to do a lot of heavy lifting for us - e.g. it will make the query available to the client without any extra code, all that developer needs to do is import it in their React component. Another big thing is cache invalidation / automatic re-fetching of the query once the data changes (this is why it is important to declare which entities it depends on).

The remaining step is to write the function with the query logic:

ext/queries.js | Query logic, using Prisma SDK via Node.js
export const getListsAndCards = async (args, context) => {  // Only authenticated users can execute this query.  if (!context.user) { throw new HttpError(403) }
  return context.entities.List.findMany({    // We want to make sure user can access only their own cards.    where: { user: { id: context.user.id } },    include: { cards: true }  })}

This is just a regular Node.js function, there are no limits on what you can return! All the stuff provided by Wasp (user data, Prisma SDK for a specific entity) comes in a context variable.

The code for actions is very similar (we just need to use action keyword instead of query) so I won't repeat it here. You can check out the code for updateCard action here.

Pages, routing & components#

To display all the nice data we have, we'll use React components. There are no limits to how you can use React components within Wasp, the only one is that each page has its root component:

main.wasp | Declaration of a page & route in Wasp
route "/" -> page Mainpage Main {    authRequired: true,    component: import Main from "@ext/MainPage.js"}

All pretty straightforward so far! As you can see here, Wasp also provides authentication out-of-the-box.

Currently, the majority of the client logic of Waspello is contained in ext/MainPage.js (we should break it down a little 😅 - you can help us!). Just to give you an idea, here's a quick glimpse into it:

ext/MainPage.js | Using React component in Wasp
// "Special" imports provided by Wasp.import { useQuery } from '@wasp/queries'import getListsAndCards from '@wasp/queries/getListsAndCards'import createList from '@wasp/actions/createList'
const MainPage = ({ user }) => {  // Fetching data via useQuery.  const { data: listsAndCards, isFetchingListsAndCards, errorListsAndCards }    = useQuery(getListsAndCards)
  // A lot of data transformations and sub components.  ...
  // Display lists and cards.  return (    ...  )}

Once you've defined a query or action as described above, you can immediately import it into your client code as shown in the code sample, by using the @wasp prefix in the import path. useQuery ensures reactivity so once the data changes the query will get re-fetched. You can find more details about it here.

This is pretty much it from the stuff that works 😄 ! I kinda rushed a bit through things here - for more details on all Wasp features and to build your first app with Wasp, check out our docs.

What doesn't work (yet)#

The main problem of the current implementation of Waspello is the lack of support for optimistic UI updates in Wasp. What this means is that currently, when an entity-related change is made (e.g. a card is moved from one list to another), we have to wait until that change is fully executed on the server until it is visible in the UI, which causes a noticeable delay.
In many cases that is not an issue, but when UI elements are all visible at once and it is expected from them to be updated immediately, then it is noticeable. This is also one of the main reasons why we chose to work on Waspello - to have a benchmark/sandbox for this feature! Due to this issue, here's how things currently look like:

Waspello - no optimistic UI update
Without an optimistic UI update, there is a delay

You can notice the delay between the moment the card is dropped on the "Done" list and the moment it becomes a part of that list. The reason is that at the moment of dropping the card on "Done" list, the API request with the change is sent to the server, and only when that change is fully processed on the server and saved to the database, the query getListsAndCards returns the correct info and consequently, UI is updated to the correct state.
That is why upon dropping on "Done", the card first goes back to the original list (because the change is not saved in db yet, so useQuery(getListsAndCards) still returns the "old" state), it waits a bit until the API request is processed successfully, and just then the change gets reflected in the UI.

The solution#

A typical approach for solving this issue is to make the client a bit more self-confident, in a way that it doesn't wait for the confirmation from the server but rather immediately updates the UI, at the same time or even before the API request is fired. If it then turns out something went wrong on the server (which typically shouldn't happen), it reverses the change and shows an error message. Thus the name optimistic UI update, since the client assumes in advance that everything will go well to provide a nicer UX.

Waspello - the client being brave
The client when performing an optimistic UI update

This is one of the most complex and error-prone features when developing web apps today and that is why we are super excited to tackle it in Wasp and make the experience as smooth as possible! We are currently in the "figuring out the solution" stage and you can track/join the discussion on GitHub!

What's missing (next features)#

Although it looks super simple at the first glance, Trello is in fact a huge app with lots and lots of cool features hidden under the surface! Here are some of the more obvious ones that are currently not supported in Waspello:

  • Users can have multiple boards, for different projects (currently we have no notion of a "Board" entity in Waspello at all, so there is implicitly only one)
  • Detailed card view - when clicked on a card, a "full" view with extra options opens
  • Search - user can search for a specific list/card
  • Collaboration - multiple users can participate on the same board

And many more - e.g. support for workspaces (next level of the hierarchy, a collection of boards), card labels, filters, ... . It is very helpful to have such a variety of features since we can use it as a testing ground for Wasp and use it as a guiding star towards Beta/1.0!

Become a Waspeller!#

Waspello propaganda
Lightweight Waspello propaganda

If you want to get involved with OSS and at the same time familiarize yourself with Wasp, this is a great way to get started - feel free to choose one of the features listed here or add your own and help us make Waspello the best demo productivity app out there!

Also, make sure to join our community on Discord. We’re always there and are looking forward to seeing what you build!

We are in Alpha (try it out)!Join our communityWork with us

· 8 min read

Wasp fundraise chart

Wasp is a configuration language (DSL) for building full-stack web apps with less code and best practices that works alongside React and Node.js. We are on a mission to streamline web app development while empowering developers to continue using the power of code and their favorite tools. We are backed by Y Combinator and engineers from Airbnb, Facebook, and Lyft.

We are in Alpha (try it out)!Join our communityWork with us

Wasp was part of Y Combinator’s W21 batch, which took place from January of 2021 until the end of March.

At Demo Day, our product had a solid traction (200+ projects created, 1k Github stars, good ProductHunt and HackerNews feedback) but no monetisation yet, which is typical for open-source projects at this stage. Being based in the EU, we also didn't have a huge network in the Bay Area prior to the fundraise.

caution

I will try to refrain from giving "general" advice (as our fundraise is a single data point), and focus on the stats and specific things that worked for us. Keep in mind the same might not work for you - I recommend always taking advice with a pinch of salt to see what makes the most sense in your case.

As we approached our fundraise, we didn't really know what to expect. We had friends from the previous batch that raised a big round very quickly (even before Demo Day) and heard a couple of stories from a few other YC founders who were also quite successful, so we imagined it might go quickly for us too.

As you can see from the title, we had quite a journey with plenty of meetings that provided us a lot of input on how to improve our pitch, and maybe even more importantly, how to reach the right investors.

Here are our stats:

  • we spoke to 212 investors → that led to 250+ meetings
  • 98 days passed between the first and the last signed SAFE
  • 171 investor passed, 24 never responded, 17 invested

And here is how it all looked when laid out on a timeline: Wasp fundraise chart

Here are some of the things that worked for us:

We treated fundraising as a sales process (and stuck to it)#

Wasp fundraise funnel

This means we had a typical sales funnel - lead generation, selling (pitching) and following up:

  • Lead generation: it started with Demo Day of course, from which we got 100+ leads but none of them ended up investing (more on that below). After that we mainly relied on our YC batchmates to identify relevant investors and get the intros.
  • Pitching: we did a conversational pitch without the deck, but we had a Notion one-pager from which I would drop links during the conversation (to e.g. our traction chart, user testimonials etc.). It also worked well as investors would typically find it interesting and keep scrolling through as we talked, asking follow-up questions.
  • Following-up: we followed up once per week. I would usually "batch process" it each Wednesday. We used Streak to identify all the leads that I haven't heard from in over 7 days (there is a filter for that) and then manually emailed them.

We started with tracking everything in Google Sheets, but with the volume of leads it soon became hard to navigate them through the funnel. Then we switched to Streak (used their fundraising template, and modified it a bit) and that worked great. The most helpful thing for me was having a CRM that is integrated with gmail, that made the process much more seamless and gave us better overview of the funnel. As soon as I would receive an email I could see in which stage the investor is, and it was also super easy to add new investors straight from gmail - it saved us from the dreaded context switching and kept us focused.

Our pitch became much better after ~50 meetings#

We kept being critical of our pitch and kept a list of questions that we felt needed more work. We called it "creating narratives", e.g. why the right time for our product is now, presenting the team, or how we plan to monetise. We talked to other companies in the same space (devtools, OSS), investigated comparatives (big companies we compared ourselves too), talked to our angels who were domain experts and used all that to build a more convincing story.

I never intended to learn our pitch by heart, but after delivering it for 100s of times just that happened - both me and Martin (my brother and cofounder, who wasn't pitching but was always sitting behind me and provided feedback, especially in the beginning) knew it word by word and I realised how much more polished it sounds and how much more confident I felt compared to when we just started.

Our goal was to get to 100 no's#

After about 50 meetings (and about 20 VCs having passed on us) we started feeling a bit disheartened, as things didn't seem to go so easy as we initially expected. Then I chatted to a friend who also recently finished their fundraise and he gave me a tour of Streak - I saw their numbers and that over 150 investors passed on them! With that I realised our 20 passes were just the beginning and that instead of chasing yeses we should actually chase no's :) - they are more predictable, you'll get plenty of them and they will clearly show your progress.

We had 100+ leads from Demo Day - none of them invested#

This is probably pretty specific for our case, but it's how it went. Connecting with a startup on Demo Day is a very low-cost action for investors. Also, as many investors as there are on Demo Day, there are even more of them who aren't.

When we sorted through the connections we got, about 20% were a really good fit for us, meaning they invest in deep tech / OSS companies, have invested recently, invest in our stage etc.

We still met with pretty much all the interested leads, but we quickly realised that due to our product being deeply technical and the company being pre-revenue, only investors with engineering backgrounds were really interested because they could understand and get excited about what we do. That informed us to generate our leads with much narrower focus.

We looked at other OSS & dev tools companies in our batch, looked at who invested in them and asked for intros. Our batchmates were also in the fundraising mode, they knew how hard it can be and they wanted to help, so everything moved very quickly.

We learned not to spend time on non-believers#

As we learned to focus on the highly qualified leads, we also learned that it is very hard (impossible) to change somebody's mind. Plenty of investors liked u and what we do, but they were skeptical about e.g. market size or monetisation potential and made that clear from the start. Many of them were keen to keep chatting, wanted to meet our angel investors etc., but none of that helped change their mind and it was very distracting for us. I believe it is very hard to change somebody's worldview, especially in the seed stage when there is often no strong factual evidence to do so.

Passing through the "valley of death"#

As you can see on the chart, about two months in we barely passed $300k, and we had a whole month with no progress. At the same time, we felt that our pitch got significantly better and we were reaching investors much better suited for us. It was one of the most difficult times, seeing others close their rounds, but we decided to trust in the process and keep going until we have used all the resources we had. It was also the time our lead investor took time to do their own pretty extensive due diligence on Wasp, so although it looks like no progress was made from the outside, a lot of stuff was actually happening behind the scenes.

Suddenly, a few things clicked together from multiple sides and our round was quickly closed, even oversubscribed! It was truly a magical feeling to start closing investors in a single day, even during the first call, when previously it took us weeks to close our first $50k check. The big factor was also that our round was getting filled up and that of course motivated investors to move faster.

We compared ourselves to big, successful companies#

This is one of the best pieces of advice we got from YC partners about fundraising. In the beginning we didn't understand how important this was, but once the meetings started we realised this was one of the best ways to explain the potential of our company to investors. With the innovation in technology that isn't easy to grasp, they needed something to hold on to understand how the business model and distribution could work, and it sounds much more doable if there is a playbook we can follow rather than us reinventing that as well. We kept working on finding a good comparable (we had a few) and explaining in which ways we are similar and why.

Good luck - you can do it!#

I hope you found this helpful and that our story will motivate you to keep going once things get hard! We wish you the best of luck and also feel free to reach out if you'll have any questions.

We are in Alpha (try it out)!Join our communityWork with us

· 5 min read

Wasp is a configuration language (DSL) for building full-stack web apps with less code and best practices that works alongside React and Node.js. We are on a mission to streamline web app development while empowering developers to continue using the power of code and their favorite tools. We are backed by Y Combinator and engineers from Airbnb, Facebook, and Lyft.

We are in Alpha (try it out)!Join our communityWork with us

After graduating from Y Combinator's Winter 2021 Batch, we are super excited to announce that Wasp raised $1.5m in our first funding round! The round is led by Lunar Ventures and joined by HV Capital. Also see it in TechCrunch.

The best thing about it is that the majority of our investors are either experienced engineers themselves (e.g. ex-Facebook, Twitter and Airbnb) or have a strong focus on investing in deep technology and developer companies. They share the vision we have with Wasp, understand and care about the problem we are solving.

Besides Lunar and HV Capital, we are thrilled to welcome on board:

  • 468 Capital (led by Florian Leibert, founder of Mesosphere and ex-Twitter and Airbnb eng.)
  • Charlie Songhurst
  • Tokyo Black
  • Acequia Capital
  • Abstraction Capital
  • Ben Tossell, founder of Makerpad (acq. by Zapier)
  • Muthukrishnan Ramabadran, Senior Software Engineer at Lyft
  • Yun-Fang, ex-Facebook engineer
  • Marcel P. Lima from Heller House
  • Chris Schagen, former CMO on Contentful
  • Rahul Thathoo, Sr. Eng. Manager at Square
  • Preetha Parthasarathy
  • John Kobs

Why did we raise funding?#

At its core, Wasp is an open-source project and we have full intention for it to stay that way. Open-source is one of the most powerful ways to write software and we want to make sure Wasp is freely accessible to every developer.

Wasp is a technically innovative and challenging project. Even though we are not building a new general programming language from scratch, there still exists an essential complexity of building a language and all the tooling around it. Wasp offers a lot of abstractions that are being introduced for the first time and there is no clear blueprint to follow, and this is why such an undertaking requires full-time attention and dedication. Hence, we plan on expanding the team with some amazing engineers to accelerate us on our journey.

Where are we today?#

Today, Wasp is in Alpha. That means there are many features we still have to add and many that are probably going to change. But it also means you can try it out, build a full-stack web app and see what it is all about. You can also join our community and share your feedback and experience with us - we'd be happy to hear from you!

Since we launched our Alpha several months ago, we got some amazing feedback on Product Hunt and Hacker News.

We've also grown a lot and recently passed 1,000 stars on our Github repo - thank you!

Wasp GitHub Stars

To date, over 250 projects have been created with Wasp in the last couple of months and some were even deployed to production - like Farnance that ended up being a hackathon winner! Check out their source code here.

Farnance screenshot

The team#

Martin and I have been working on Wasp for the last two years and together with our amazing contributors, who made us believe our vision is possible and made it what it is today. Having led development of several complex web apps in the past and continuously switching to the latest stack, we felt the pain and could also clearly see the patterns that we felt were mature and common enough to be worth extracting into a simpler, higher-level language.

The team
Martin and I during our first YC interview. Read here for more details on our journey to YC!

In case you couldn't tell from the photo and our identical glasses, we are twins (but not fraternal ones, and I'm a couple of minutes older, which makes me CEO :D)!

We are coming from the background of C++, algorithm competitions and applied algorithms in bioinformatics (Martin built edlib, his first OSS project - a popular sequence alignment library used by top bioinfo companies like PacBio) and did our internships in Google and Palantir. There we first encountered the modern web stack and went on to lead development of web platforms in fintech and bioinformatics space. We also had a startup previously (TalkBook), where we learned a lot about talking to users and building something that solves a problem they have.

What comes next?#

With the funding secured, we can now fully focus on developing Wasp and the ecosystem around it. We can start planning for more long-term features that we couldn't fully commit to until now, and we can expand our team to move faster and bring more great people on board with new perspectives and enable them to fully employ their knowledge and creativity without any distractions.

Our immediate focus is to bring Wasp to Beta and then 1.0 (see our high-level roadmap here), while also building a strong foundation for our open source community. We believe community is the key to the success for Wasp and we will do everything in our power to make sure everybody feels welcome and has a fun and rewarding experience both building apps and contributing to the project. If you want to shape how millions of engineers develop the web apps of tomorrow, join our community and work with us!

Thank you for reading - we can't wait to see what you will build!

We are in Alpha (try it out)!Join our communityWork with us

· 9 min read

You might have seen forall being used in Haskell like this:

f :: forall a. [a] -> [a]f xs = ys ++ ys  where ys :: [a]        ys = reverse xs

or

liftPair :: (forall x. x -> f x) -> (a, b) -> (f a, f b)

or

data Showable = forall s. (Show s) => Showable s

forall is something called "type quantifier", and it gives extra meaning to polymorphic type signatures (e.g. :: a, :: a -> b, :: a -> Int, ...).

While normaly forall plays a role of the "universal quantifier", it can also play a role of the "existential quantifier" (depends on the situation).

What does all this mean and how can forall be used in Haskell? Read on to find out!

NOTE: we assume you are comfortable with basic polymorphism in Haskell.

Quick math/logic reminder#

In mathematical logic, we have

  • universal quantifier
    • symbol: ∀x
    • interpretation: "for all", "given any"
    • example: ∀x P(x) means "for all x predicate P(x) is true".
  • existential quantifier
    • symbol: ∃x
    • interpretation: "there exists", "there is at least one", "for some"
    • example: ∃x P(x) means "there is some x for which predicate P(x) is true".

Vanilla Haskell (no extensions)#

In Haskell, all polymorphic type signatures are considered to be implicitly prefixed with forall.

Therefore, if you have

f :: a -> ag :: a -> (a -> b) -> b

it is really the same as

f :: forall a. a -> ag :: forall a b. a -> (a -> b) -> b

What forall here does is play the role of universal quantifier. For function f, it means it is saying "for all types, this function takes that type and returns the same type.". Other way to put it would be "this funtion can be called with value of any type as its first argument, and it will return the value of that same type".

Since forall is already implicit, writing it explicitly doesn't really do anything!

Not only that, but without any extensions, you can't even write forall explicitly, you will get a syntax error, since forall is not a keyword in Haskell.

So what is the purpose of forall then? Well, obviously to be used with extensions :)!

The simplest extension is ExplicitForAll, which allows you to explicitly write forall (as we did above). This is not useful on its own though, since as we said above, explicitly writing forall doesn't change anything, it was already implicitly there.

However, there are other extensions that make use of forall keyword, like: ScopedTypeVariables, RankNTypes, ExistentialQuantification. All these extensions automatically enable ExplicitForAll extension, which means you don't need to enable it yourself when using any of these. There is also TypeApplications extension which interacts with forall and in that case you might want to use ExplicitForAll with it.

Since forall is useful only when used with extensions, let's take a look at how it is used in each one of those!

forall and extension ScopedTypeVariables#

ScopedTypeVariables enables lexical scoping of type variables by explicitly introducing them with forall.

Let's take a look at the following example:

f :: [a] -> [a]f xs = ys ++ ys  where ys :: [a]        ys = reverse xs

This code does not compile, because compiler can't match type of ys with the return type of f. Why though, when they are both [a]? Well, that is because that is not the same a! Try changing ys :: [a] to ys :: [b] and you will get the exact same error, because it is exactly the same code -> a in ys :: [a] and a in f :: [a] -> [a] are different as and there is no connection between them. a in ys :: [a] stands for "any type", not for "that type that is reffered to with a in the type signature above".

This is where ScopedTypeVariables comes in:

{-# LANGUAGE ScopedTypeVariables #-}
f :: forall a. [a] -> [a]f xs = ys ++ ys  where ys :: [a]        ys = reverse xs

forall now gets special powers: the type variables bound by a forall (in our case a) scope over the entire definition of the accompanying value declaration (in this case definition of f).

This means that any mention of type a in the definition of f now refers to that a from the type signature of f, which is exactly what we needed, and the code example above now compiles.

forall and extension RankNTypes#

Basically, what RankNTypes does is enable you to use forall nested in type signatures, so that it does not apply to the whole type signature but just the part of it.

This enables some cool things that you were not able to do to before, for example you can specify that your function takes a polymorphic function as an argument.

Take a look at this example:

foo :: (forall a. a -> a) -> (Char, Bool)    -- We can do this only with RankNTypes.
bar :: forall a. ((a -> a) -> (Char, Bool))  -- This is usual stuff, we don't need RankNTypes for it. Actually we can even drop `forall` since it is implicit.

In foo, forall is applied only to the first argument of foo, which is a -> a, and not to the rest of the f's type signature. This can be done only with RankNTypes extension. bar on the other hand has forall applied to the whole signature, and we could have even ommited this forall since it would be there implicitly anyway.

Now, what does this mean? If we now have specificFunc :: Int -> Int and polymorphicFunc :: a -> a, foo polymorphicFunc will compile, while foo specificFunc will not! On the other hand both bar specificFunc and bar polymorphicFunc will compile.

This is because we specified, with forall, that foo needs a polymorphic function (function that takes value of any type and returns value of that same type) as the first argument, so we can't pass it a function like specificFunc that works only for Int -> such function is too specific. On the other hand, bar needs a function that takes value of some type and returns the value of that same type, so specificFunc is completely fine since it works only with Int, while polymorphicFunc is also ok although it is more general than what is needed, since compiler can easily specialize it.

Another example is liftPair function:

liftPair :: (forall x. x -> f x) -> (a, b) -> (f a, f b)liftPair func (y, z) = (func y, func z)
>> liftPair (:[]) (1, "a")([1], ["a"])

liftPair takes polymorphic function and applies it to both values in the pair. There would be no way to write its type signature without using RankNTypes.

forall and extension ExistentialQuantification#

ExistentialQuantification enables us to use forall in the type signature of data constructors.

This is useful because it enables us to define heterogeneous data types, which then allows us to store different types in a single data collection (which normally you can't do in Haskell, e.g. you can't have different types in a list).

For example, if we have

data Showable = forall s. (Show s) => Showable s

now we can do

someShowables :: [Showable]someShowables = [Showable "Hi", Showable 5, Showable (1, 2)]
printShowables :: [Showable] -> IO ()printShowables ss = mapM_ (\(Showable s) -> print s) ss
main :: IO ()main = printShowables someShowables

In this example this allowed us to create a heterogeneous list, but only thing we can do with the contents of it is show them.

What is interesting is that in this case, forall plays the role of an existential quantifier (therefore the name of extension, ExistentialQuantification), unlike the role of universal quantifier it normally plays.

GADTs#

Alternative approach to ExistentialQuantification is to use the GADTs extension, like this:

{-# LANGUAGE GADTs #-}data Showable where  Showable :: (Show s) => s -> Showable

In this case forall is not needed, as it is implicit.

forall and extension TypeApplications#

TypeApplications does not change how forall works like the extensions above do, but it does have an interesting interaction with forall, so we will mention it here.

TypeApplications allows you to specify values of types variables in a type.

For example, you can do show (read @Int "5") to specify that "5" should be interpreted as an Int. read has type signature :: Read a => String -> a, so what @Int does is say that that a in the type signature is Int. Therefore, read @Int :: String -> Int.

How does forall come into play here?

Well, if an identifier’s type signature does not include an explicit forall, the type variable arguments appear in the left-to-right order in which the variables appear in the type. So, foo :: Monad m => a b -> m (a c) will have its type variables ordered as m, a, b, c, and type applications will happen in that order: if we have foo @Maybe @Either, @Maybe will apply to m while @Either will apply to a. However, if you want to force a different order, for example a, b, c, m, so that @Maybe in foo @Maybe @Either applies to a, you can refactor the signature as foo :: forall a b c m. Monad m => a b -> m (a c), and now order of type variables in forall will be used when doing type applications!

This will require you to enable ExplicitForAll extension, if it is not already enabled.

Conclusion

This document should give a fair idea of how forall is used and what can be done with it, but it doesn't go into much depth or cover all of the ways forall is used in Haskell.

For more in-detail explanations and further investigation, here is a couple of useful resources:

This blog post originated from the notes I wrote in wasp-lang/haskell-handbook.

· 9 min read

Guest introducing themselves and getting full-access.
A Guest user getting access by introducing themselves in the "introductions" channel.

At Wasp, we have a Discord server for our community, where we talk with people interested in and using Wasp - Waspeteers!

In the beginning, we knew everybody in the community by their name, but as it started growing, we had a lot of people joining that never wrote anything, and the community started feeling less homey, less intimate.

This was when we decided to make it required for the new members to introduce themselves to gain access to the community. We knew that with this kind of barrier we would probably lose some potential new Waspeteers, but those that would go through it would be more engaged and better integrated.

We found no other way to accomplish this automatically but to implement our own Discord bot. In this post I will describe in detail how we did it.

· 7 min read

Wasp logo

For the last year and a half, my twin brother and I have been working on Wasp: a new programming language for developing full-stack web apps with less code.

Wasp is a simple declarative language that makes developing web apps easy while still allowing you to use the latest technologies like React, Node.js, and Prisma.

In this post, I will share with you why we believe Wasp could be a big thing for web development, how it works, where we are right now and what is the plan for the future!

· 6 min read

About a year or so ago, brother and I started discussing how awesome it would be to have a programming language that would understand what “web app” means. Such language would, on one hand, serve as an expressive specification of the web app, while on the other hand, it would take care of “boring” work for us, while we could focus on the business logic specific for our web app.

Step by step, the idea has started to take a more concrete shape, and Wasp (Web Application SPecification language) came to life! While still very early, we are writing this blog post to explain why are we building Wasp, what is the current status and what the future may hold.