Repository: gluon-lang/gluon
Pull request #595 (2018-07-31 11:22:27)

feat: Add seq expressions
This adds the `seq EXPR1 in EXPR2` syntax which desugars to `flat_map (\_ -> EXPR2) EXPR1`. In the long run we should probably desugar this to

```
let x = lazy (\expr -> EXPR2)
flat_map x EXPR1
// or with **>> : [Applicative f] -> f a -> Lazy (f b) -> f b
// EXPR1 **>> x
```

cc #566

ebkalderon: Is this PR still being worked on? This is a pretty nice ergonomics improvement to Gluon that I would hope to see land someday.
Marwes: @ebkalderon It is still on the table but I found out that `do EXPR in EXPR` and `do PATTERN = EXPR in EXPR` are ambiguous so I need another keyword for it (`seq EXPR in EXPR` is the best I got atm).

Patch:
diff --git a/README.md b/README.md
index 213f785e..7060b8f9 100644
--- a/README.md
+++ b/README.md
@@ -184,9 +184,9 @@ do digits =
     wrap [a, b, c, d]
 
 let print_digits = for digits (\d ->
-        do _ = io.print " "
+        seq io.print " "
         io.print (show d))
-do _ = io.print "Four digits:" *> print_digits *> io.println ""
+seq io.print "Four digits:" *> print_digits *> io.println ""
 
 let guess_loop _ =
     do line = io.read_line
diff --git a/base/src/ast.rs b/base/src/ast.rs
index 35d62405..bfed2d13 100644
--- a/base/src/ast.rs
+++ b/base/src/ast.rs
@@ -274,7 +274,7 @@ pub struct ExprField<Id, E> {
 
 #[derive(Clone, PartialEq, Debug)]
 pub struct Do<Id> {
-    pub id: SpannedIdent<Id>,
+    pub id: Option<SpannedPattern<Id>>,
     pub bound: Box<SpannedExpr<Id>>,
     pub body: Box<SpannedExpr<Id>>,
     pub flat_map_id: Option<Box<SpannedExpr<Id>>>,
@@ -685,7 +685,9 @@ pub fn walk_expr<'a, V: ?Sized + $trait_name<'a>>(v: &mut V, e: &'a $($mut)* Spa
             ref $($mut)* body,
             ref $($mut)* flat_map_id,
         }) => {
-            v.visit_spanned_typed_ident(id);
+            if let Some(id) = id {
+                v.visit_pattern(id);
+            }
             v.visit_expr(bound);
             v.visit_expr(body);
             if let Some(ref $($mut)* flat_map_id) = *flat_map_id {
diff --git a/base/src/scoped_map.rs b/base/src/scoped_map.rs
index 71e83a69..9b52d444 100644
--- a/base/src/scoped_map.rs
+++ b/base/src/scoped_map.rs
@@ -1,6 +1,7 @@
 //! A map data type which allows the same key to exist at multiple scope levels
 use std::borrow::Borrow;
-use std::collections::hash_map::{self, IterMut};
+use std::collections::hash_map;
+use std::collections::hash_map::{Entry, IterMut};
 use std::fmt;
 use std::hash::Hash;
 
@@ -130,13 +131,6 @@ impl<K: Eq + Hash + Clone, V> ScopedMap<K, V> {
         self.map.get(k).map(|x| &x[..])
     }
 
-    pub fn entry(&mut self, key: K) -> Entry<K, V> {
-        match self.map.entry(key) {
-            hash_map::Entry::Occupied(entry) => Entry::Occupied(OccupiedEntry(entry)),
-            hash_map::Entry::Vacant(entry) => Entry::Vacant(VacantEntry(entry)),
-        }
-    }
-
     pub fn contains_key<'a, Q: ?Sized>(&'a self, k: &Q) -> bool
     where
         K: Borrow<Q>,
@@ -165,8 +159,8 @@ impl<K: Eq + Hash + Clone, V> ScopedMap<K, V> {
     /// Swaps the value stored at key, or inserts it if it is not present
     pub fn swap(&mut self, k: K, v: V) -> Option<V> {
         let vec = match self.map.entry(k.clone()) {
-            hash_map::Entry::Occupied(v) => v.into_mut(),
-            hash_map::Entry::Vacant(v) => v.insert(Vec::new()),
+            Entry::Occupied(v) => v.into_mut(),
+            Entry::Vacant(v) => v.insert(Vec::new()),
         };
         if vec.is_empty() {
             vec.push(v);
@@ -204,8 +198,8 @@ impl<K: Eq + Hash + Clone, V> ScopedMap<K, V> {
 
     pub fn insert(&mut self, k: K, v: V) -> bool {
         let vec = match self.map.entry(k.clone()) {
-            hash_map::Entry::Occupied(v) => v.into_mut(),
-            hash_map::Entry::Vacant(v) => v.insert(Vec::new()),
+            Entry::Occupied(v) => v.into_mut(),
+            Entry::Vacant(v) => v.insert(Vec::new()),
         };
         vec.push(v);
         self.scopes.push(Some(k));
@@ -237,42 +231,6 @@ impl<K: Eq + Hash + Clone, V> Extend<(K, V)> for ScopedMap<K, V> {
     }
 }
 
-pub enum Entry<'a, K, V> {
-    Vacant(VacantEntry<'a, K, V>),
-    Occupied(OccupiedEntry<'a, K, V>),
-}
-
-pub struct VacantEntry<'a, K, V>(hash_map::VacantEntry<'a, K, Vec<V>>);
-pub struct OccupiedEntry<'a, K, V>(hash_map::OccupiedEntry<'a, K, Vec<V>>);
-
-impl<'a, K, V> VacantEntry<'a, K, V> {
-    pub fn insert(self, value: V) -> &'a mut V {
-        &mut self.0.insert(vec![value])[0]
-    }
-}
-
-impl<'a, K, V> OccupiedEntry<'a, K, V> {
-    pub fn key(&self) -> &K {
-        self.0.key()
-    }
-
-    pub fn get(&self) -> &V {
-        self.0.get().last().unwrap()
-    }
-
-    pub fn get_mut(&mut self) -> &mut V {
-        self.0.get_mut().last_mut().unwrap()
-    }
-
-    pub fn into_mut(self) -> &'a mut V {
-        self.0.into_mut().last_mut().unwrap()
-    }
-
-    pub fn insert(&mut self, value: V) {
-        self.0.get_mut().push(value);
-    }
-}
-
 pub struct Iter<'a, K, V>
 where
     K: 'a,
diff --git a/book/src/syntax-and-semantics.md b/book/src/syntax-and-semantics.md
index 9bfd0acd..f9266e83 100644
--- a/book/src/syntax-and-semantics.md
+++ b/book/src/syntax-and-semantics.md
@@ -323,6 +323,21 @@ flat_map (\x -> Some (x + 2)) (Some 1)
 
 do x = Some 1
 Some (x + 2)
+
+// The binding can also be a (irrefutable) pattern
+do { y } = Some { y = "" }
+Some y
+```
+
+### Seq expressions
+
+`seq` expressions work just like `do` expressions, only they do not have a binding.
+
+```f#,rust
+let io @ { ? } = import! std.io
+seq io.print "Hello"
+seq io.print " "
+io.println "world!"
 ```
 
 ### Indentation
diff --git a/check/src/implicits.rs b/check/src/implicits.rs
index 51281e4c..71797425 100644
--- a/check/src/implicits.rs
+++ b/check/src/implicits.rs
@@ -1,9 +1,7 @@
-use std::{
-    borrow::Borrow,
-    cmp::Ordering,
-    fmt,
-    hash::{Hash, Hasher},
-};
+use std::borrow::Borrow;
+use std::cmp::Ordering;
+use std::fmt;
+use std::hash::{Hash, Hasher};
 
 use itertools::{Either, Itertools};
 
@@ -11,23 +9,20 @@ use rpds;
 
 use codespan_reporting::{Diagnostic, Label};
 
-use crate::base::{
-    ast::{self, Expr, MutVisitor, SpannedExpr, TypedIdent},
-    error::AsDiagnostic,
-    fnv::FnvMap,
-    metadata::Metadata,
-    pos::{self, BytePos, Span, Spanned},
-    resolve,
-    scoped_map::{self, ScopedMap},
-    symbol::{Symbol, SymbolRef},
-    types::{self, ArcType, ArgType, BuiltinType, Type},
-};
-
-use crate::{
-    substitution::Substitution,
-    typecheck::{TypeError, Typecheck, TypecheckEnv},
-    unify_type::{self, Size},
-};
+use crate::base::ast::{self, Expr, MutVisitor, SpannedExpr, TypedIdent};
+use crate::base::error::AsDiagnostic;
+use crate::base::fnv::FnvMap;
+use crate::base::metadata::Metadata;
+use crate::base::pos::{self, BytePos, Span, Spanned};
+use crate::base::resolve;
+use crate::base::scoped_map::ScopedMap;
+use crate::base::symbol::{Symbol, SymbolRef};
+use crate::base::types::{self, ArcType, ArgType, BuiltinType, Type};
+
+use crate::substitution::Substitution;
+use crate::typecheck::{TypeError, Typecheck, TypecheckEnv};
+
+const MAX_IMPLICIT_LEVEL: u32 = 20;
 
 impl SymbolKey {
     pub fn new(typ: &ArcType) -> Option<SymbolKey> {
@@ -156,10 +151,7 @@ impl ImplicitBindings {
         update_vec(&mut self.rest, &mut f);
     }
 
-    fn get_candidates<'a>(
-        &'a self,
-        typ: &ArcType,
-    ) -> impl DoubleEndedIterator<Item = &'a ImplicitBinding> {
+    fn iter<'a>(&'a self, typ: &ArcType) -> impl DoubleEndedIterator<Item = &'a ImplicitBinding> {
         match SymbolKey::new(&typ) {
             Some(symbol) => Either::Left(self.partioned.get(&symbol).unwrap_or(&self.rest).iter()),
             None => Either::Right(
@@ -244,86 +236,6 @@ struct ResolveImplicitsVisitor<'a, 'b: 'a> {
 }
 
 impl<'a, 'b> ResolveImplicitsVisitor<'a, 'b> {
-    fn resolve_implicit(
-        &mut self,
-        implicit_bindings: &ImplicitBindings,
-        expr: &SpannedExpr<Symbol>,
-        id: &TypedIdent<Symbol>,
-    ) -> Option<SpannedExpr<Symbol>> {
-        debug!(
-            "Resolving {} against:\n{}",
-            id.typ,
-            implicit_bindings
-                .get_candidates(&id.typ)
-                .map(|t| &t.1)
-                .format("\n")
-        );
-        self.tc.implicit_resolver.visited.clear();
-        let span = expr.span;
-        let mut to_resolve = Vec::new();
-        match self.find_implicit(
-            &implicit_bindings,
-            &mut to_resolve,
-            &Demand {
-                reason: rpds::List::new(),
-                constraint: id.typ.clone(),
-            },
-        ) {
-            Ok(path_of_candidate) => {
-                debug!(
-                    "Found implicit candidate `{}`. Trying its implicit arguments (if any)",
-                    path_of_candidate
-                        .iter()
-                        .rev()
-                        .map(|id| &id.name)
-                        .format(".")
-                );
-
-                let resolution_result = match self.resolve_implicit_application(
-                    0,
-                    &implicit_bindings,
-                    span,
-                    &path_of_candidate,
-                    &to_resolve,
-                ) {
-                    Ok(opt) => opt.map(Ok),
-                    Err(err) => Some(Err(err)),
-                };
-
-                match resolution_result {
-                    Some(Ok(replacement)) => Some(replacement),
-                    Some(Err(err)) => {
-                        self.tc.errors.push(Spanned {
-                            span: expr.span,
-                            value: TypeError::UnableToResolveImplicit(err).into(),
-                        });
-                        None
-                    }
-                    None => {
-                        debug!("UnableToResolveImplicit {:?} {}", id.name, id.typ);
-                        self.tc.errors.push(Spanned {
-                            span: expr.span,
-                            value: TypeError::UnableToResolveImplicit(Error {
-                                kind: ErrorKind::MissingImplicit(id.typ.clone()),
-                                reason: to_resolve
-                                    .first()
-                                    .map_or_else(rpds::List::new, |demand| demand.reason.clone()),
-                            })
-                            .into(),
-                        });
-                        None
-                    }
-                }
-            }
-            Err(err) => {
-                self.tc.errors.push(Spanned {
-                    span: expr.span,
-                    value: TypeError::UnableToResolveImplicit(err).into(),
-                });
-                None
-            }
-        }
-    }
     fn resolve_implicit_application(
         &mut self,
         level: u32,
@@ -349,6 +261,16 @@ impl<'a, 'b> ResolveImplicitsVisitor<'a, 'b> {
         path: &[TypedIdent<Symbol>],
         to_resolve: &[Demand],
     ) -> Result<Option<SpannedExpr<Symbol>>> {
+        match to_resolve.first() {
+            Some(demand) if level > MAX_IMPLICIT_LEVEL => {
+                return Err(Error {
+                    kind: ErrorKind::LoopInImplicitResolution(Vec::new()),
+                    reason: demand.reason.clone(),
+                });
+            }
+            _ => (),
+        }
+
         let base_ident = path[0].clone();
         let func = path[1..].iter().fold(
             pos::spanned(span, Expr::Ident(base_ident)),
@@ -366,8 +288,6 @@ impl<'a, 'b> ResolveImplicitsVisitor<'a, 'b> {
             let resolved_arguments = to_resolve
                 .iter()
                 .filter_map(|demand| {
-                    self.tc.implicit_resolver.visited.enter_scope();
-
                     let mut to_resolve = Vec::new();
                     let result = self
                         .find_implicit(implicit_bindings, &mut to_resolve, demand)
@@ -382,15 +302,12 @@ impl<'a, 'b> ResolveImplicitsVisitor<'a, 'b> {
                             )
                         });
 
-                    self.tc.implicit_resolver.visited.exit_scope();
-
                     match result {
                         Ok(opt) => opt.map(Ok),
                         Err(err) => Some(Err(err)),
                     }
                 })
                 .collect::<Result<Vec<_>>>()?;
-
             if resolved_arguments.len() == to_resolve.len() {
                 Some(pos::spanned(
                     span,
@@ -406,27 +323,25 @@ impl<'a, 'b> ResolveImplicitsVisitor<'a, 'b> {
         })
     }
 
-    fn try_resolve_implicit(
+    fn try_implicit(
         &mut self,
         path: &[TypedIdent<Symbol>],
         to_resolve: &mut Vec<Demand>,
         demand: &Demand,
-        binding_type: &ArcType,
+        typ: &ArcType,
     ) -> bool {
         debug!(
-            "Trying implicit {{\n    path: `{}`,\n    to_resolve: [{}],\n    demand: `{}`,\n    binding_type: {} }}",
+            "Trying implicit `{}` : {}",
             path.iter().map(|id| &id.name).format("."),
-            to_resolve.iter().map(|d| &d.constraint).format(", "),
-            self.tc.subs.zonk(&demand.constraint),
-            binding_type,
+            typ,
         );
 
-        let binding_type = self.tc.new_skolem_scope(binding_type);
-        let binding_type = self.tc.instantiate_generics(&binding_type);
+        let typ = self.tc.new_skolem_scope(typ);
+        let typ = self.tc.instantiate_generics(&typ);
         to_resolve.clear();
-        let mut iter = types::implicit_arg_iter(&binding_type);
+        let mut iter = types::implicit_arg_iter(&typ);
         to_resolve.extend(iter.by_ref().cloned().map(|constraint| Demand {
-            reason: demand.reason.push_front(binding_type.clone()),
+            reason: demand.reason.push_front(typ.clone()),
             constraint,
         }));
 
@@ -441,58 +356,15 @@ impl<'a, 'b> ResolveImplicitsVisitor<'a, 'b> {
         to_resolve: &mut Vec<Demand>,
         demand: &Demand,
     ) -> Result<&'c [TypedIdent<Symbol>]> {
-        let mut candidates = implicit_bindings.get_candidates(&demand.constraint).rev();
-        let found_candidate = candidates
+        let mut iter = implicit_bindings.iter(&demand.constraint).rev();
+        let found_candidate = iter
             .by_ref()
-            .find(|&&(ref path, ref typ)| self.try_resolve_implicit(path, to_resolve, demand, typ));
+            .find(|&&(ref path, ref typ)| self.try_implicit(path, to_resolve, demand, typ));
         match found_candidate {
-            Some((candidate_path, candidate_type)) => {
-                let new_demands = to_resolve
-                    .iter()
-                    .map(|d| self.tc.subs.zonk(&d.constraint))
-                    .collect::<Vec<_>>()
-                    .into_boxed_slice();
-
-                match self.tc.implicit_resolver.visited.entry(
-                    candidate_path
-                        .iter()
-                        .map(|id| id.name.clone())
-                        .collect::<Vec<_>>()
-                        .into_boxed_slice(),
-                ) {
-                    scoped_map::Entry::Vacant(entry) => {
-                        entry.insert(new_demands);
-                    }
-                    scoped_map::Entry::Occupied(mut entry) => {
-                        trace!(
-                            "Smaller check: [{}] < [{}]",
-                            new_demands.iter().format(", "),
-                            entry.get().iter().format(", "),
-                        );
-
-                        let state = unify_type::State::new(
-                            &self.tc.environment,
-                            &self.tc.subs,
-                            &self.tc.type_cache,
-                        );
-                        if !smallers(state, &new_demands, entry.get()) {
-                            return Err(Error {
-                                kind: ErrorKind::LoopInImplicitResolution(vec![candidate_path
-                                    .iter()
-                                    .map(|id| &id.name)
-                                    .format(".")
-                                    .to_string()]),
-                                reason: demand.reason.clone(),
-                            });
-                        }
-                        // Update the demands with to these new, smaller demands
-                        entry.insert(new_demands);
-                    }
-                }
-
-                let mut additional_candidates: Vec<_> = candidates
+            Some(candidate) => {
+                let mut additional_candidates: Vec<_> = iter
                     .filter(|&&(ref path, ref typ)| {
-                        self.try_resolve_implicit(path, &mut Vec::new(), demand, typ)
+                        self.try_implicit(path, &mut Vec::new(), demand, typ)
                     })
                     .map(|bind| {
                         (
@@ -502,15 +374,16 @@ impl<'a, 'b> ResolveImplicitsVisitor<'a, 'b> {
                     })
                     .collect();
                 if additional_candidates.is_empty() {
-                    Ok(&candidate_path)
+                    Ok(&candidate.0)
                 } else {
                     additional_candidates.push((
-                        candidate_path
+                        candidate
+                            .0
                             .iter()
                             .map(|id| &id.name)
                             .format(".")
                             .to_string(),
-                        candidate_type.clone(),
+                        candidate.1.clone(),
                     ));
                     Err(Error {
                         kind: ErrorKind::AmbiguousImplicit(additional_candidates),
@@ -531,7 +404,7 @@ impl<'a, 'b, 'c> MutVisitor<'c> for ResolveImplicitsVisitor<'a, 'b> {
 
     fn visit_expr(&mut self, expr: &mut SpannedExpr<Symbol>) {
         let mut replacement = None;
-        if let Expr::Ident(ref id) = expr.value {
+        if let Expr::Ident(ref mut id) = expr.value {
             let implicit_bindings = self
                 .tc
                 .implicit_resolver
@@ -539,7 +412,76 @@ impl<'a, 'b, 'c> MutVisitor<'c> for ResolveImplicitsVisitor<'a, 'b> {
                 .get(&id.name)
                 .cloned();
             if let Some(implicit_bindings) = implicit_bindings {
-                replacement = self.resolve_implicit(&implicit_bindings, expr, id);
+                debug!(
+                    "Resolving {} against:\n{}",
+                    id.typ,
+                    implicit_bindings.iter(&id.typ).map(|t| &t.1).format("\n")
+                );
+                let span = expr.span;
+                let mut to_resolve = Vec::new();
+                match self.find_implicit(
+                    &implicit_bindings,
+                    &mut to_resolve,
+                    &Demand {
+                        reason: rpds::List::new(),
+                        constraint: id.typ.clone(),
+                    },
+                ) {
+                    Ok(path_of_candidate) => {
+                        debug!(
+                            "Found implicit candidate `{}`. Trying its implicit arguments (if any)",
+                            path_of_candidate
+                                .iter()
+                                .rev()
+                                .map(|id| &id.name)
+                                .format(".")
+                        );
+
+                        let resolution_result = match self.resolve_implicit_application(
+                            0,
+                            &implicit_bindings,
+                            span,
+                            &path_of_candidate,
+                            &to_resolve,
+                        ) {
+                            Ok(opt) => opt.map(Ok),
+                            Err(err) => Some(Err(err)),
+                        };
+
+                        replacement = match resolution_result {
+                            Some(Ok(replacement)) => Some(replacement),
+                            Some(Err(err)) => {
+                                self.tc.errors.push(Spanned {
+                                    span: expr.span,
+                                    value: TypeError::UnableToResolveImplicit(err).into(),
+                                });
+                                None
+                            }
+                            None => {
+                                debug!("UnableToResolveImplicit {:?} {}", id.name, id.typ);
+                                self.tc.errors.push(Spanned {
+                                    span: expr.span,
+                                    value: TypeError::UnableToResolveImplicit(Error {
+                                        kind: ErrorKind::MissingImplicit(id.typ.clone()),
+                                        reason: to_resolve
+                                            .first()
+                                            .map_or_else(rpds::List::new, |demand| {
+                                                demand.reason.clone()
+                                            }),
+                                    })
+                                    .into(),
+                                });
+                                None
+                            }
+                        };
+                    }
+                    Err(err) => {
+                        self.tc.errors.push(Spanned {
+                            span: expr.span,
+                            value: TypeError::UnableToResolveImplicit(err).into(),
+                        });
+                    }
+                }
             }
         }
         if let Some(replacement) = replacement {
@@ -557,7 +499,6 @@ pub struct ImplicitResolver<'a> {
     environment: &'a TypecheckEnv,
     pub(crate) implicit_bindings: Vec<ImplicitBindings>,
     implicit_vars: ScopedMap<Symbol, ImplicitBindings>,
-    visited: ScopedMap<Box<[Symbol]>, Box<[ArcType]>>,
 }
 
 impl<'a> ImplicitResolver<'a> {
@@ -570,7 +511,6 @@ impl<'a> ImplicitResolver<'a> {
             environment,
             implicit_bindings: Vec::new(),
             implicit_vars: ScopedMap::new(),
-            visited: Default::default(),
         }
     }
 
@@ -719,28 +659,3 @@ pub fn resolve(tc: &mut Typecheck, expr: &mut SpannedExpr<Symbol>) {
     let mut visitor = ResolveImplicitsVisitor { tc };
     visitor.visit_expr(expr);
 }
-
-fn smaller(state: unify_type::State, new_type: &ArcType, old_type: &ArcType) -> Size {
-    match unify_type::smaller(state.clone(), new_type, old_type) {
-        Size::Smaller => match unify_type::smaller(state, old_type, new_type) {
-            Size::Smaller => Size::Different,
-            _ => Size::Smaller,
-        },
-        check => check,
-    }
-}
-fn smallers(state: unify_type::State, new_types: &[ArcType], old_types: &[ArcType]) -> bool {
-    if old_types.is_empty() {
-        true
-    } else {
-        old_types
-            .iter()
-            .zip(new_types)
-            .fold(Size::Equal, |acc, (old, new)| match acc {
-                Size::Different => Size::Different,
-                Size::Smaller => Size::Smaller,
-                Size::Equal => smaller(state.clone(), new, old),
-            })
-            == Size::Smaller
-    }
-}
diff --git a/check/src/rename.rs b/check/src/rename.rs
index 1a87888b..f8ed7448 100644
--- a/check/src/rename.rs
+++ b/check/src/rename.rs
@@ -3,7 +3,7 @@ use crate::base::{
         self, DisplayEnv, Do, Expr, MutVisitor, Pattern, SpannedAlias, SpannedAstType, SpannedExpr,
         TypedIdent,
     },
-    pos::{self, BytePos, Span},
+    pos::{self, ByteOffset, BytePos, Span},
     scoped_map::ScopedMap,
     symbol::{Symbol, SymbolModule},
     types::Type,
@@ -229,7 +229,7 @@ pub fn rename(symbols: &mut SymbolModule, expr: &mut SpannedExpr<Symbol>) {
                 }) => {
                     let flat_map = self.symbols.symbol("flat_map");
                     *flat_map_id = Some(Box::new(pos::spanned(
-                        id.span,
+                        Span::new(expr.span.end(), expr.span.start() + ByteOffset::from(2)),
                         Expr::Ident(TypedIdent {
                             name: flat_map,
                             typ: Type::hole(),
@@ -245,7 +245,9 @@ pub fn rename(symbols: &mut SymbolModule, expr: &mut SpannedExpr<Symbol>) {
 
                     self.env.stack.enter_scope();
 
-                    id.value.name = self.stack_var(id.value.name.clone(), id.span);
+                    if let Some(ref mut id) = *id {
+                        self.visit_pattern(id);
+                    }
 
                     return TailCall::TailCall;
                 }
@@ -259,6 +261,10 @@ pub fn rename(symbols: &mut SymbolModule, expr: &mut SpannedExpr<Symbol>) {
     impl<'a, 'b, 'c> MutVisitor<'c> for RenameVisitor<'a, 'b> {
         type Ident = Symbol;
 
+        fn visit_pattern(&mut self, pattern: &mut ast::SpannedPattern<Symbol>) {
+            self.new_pattern(pattern);
+        }
+
         fn visit_expr(&mut self, mut expr: &mut SpannedExpr<Self::Ident>) {
             let mut i = 0;
             loop {
diff --git a/check/src/substitution.rs b/check/src/substitution.rs
index 429f703f..5b6ef512 100644
--- a/check/src/substitution.rs
+++ b/check/src/substitution.rs
@@ -5,7 +5,8 @@ use std::fmt;
 use union_find::{QuickFindUf, Union, UnionByRank, UnionFind, UnionResult};
 
 use crate::base::fixed::{FixedMap, FixedVec};
-use crate::base::types::{self, ArcType, Type, Walker};
+use crate::base::types;
+use crate::base::types::{ArcType, Type, Walker};
 
 #[derive(Debug, PartialEq)]
 pub enum Error<T> {
@@ -397,15 +398,3 @@ impl<T: Substitutable + PartialEq + Clone> Substitution<T> {
         Ok(resolved_type.cloned())
     }
 }
-
-impl Substitution<ArcType> {
-    pub fn zonk(&self, typ: &ArcType) -> ArcType {
-        types::walk_move_type(typ.clone(), &mut |typ| match typ.get_var() {
-            Some(var) => match self.find_type_for_var(var.get_id()) {
-                Some(t) => Some(self.zonk(t)),
-                None => None,
-            },
-            None => None,
-        })
-    }
-}
diff --git a/check/src/typecheck.rs b/check/src/typecheck.rs
index 8e773e1a..4bae2d58 100644
--- a/check/src/typecheck.rs
+++ b/check/src/typecheck.rs
@@ -31,11 +31,13 @@ use crate::base::{
     },
 };
 
-use crate::implicits;
-use crate::kindcheck::{self, Error as KindCheckError, KindCheck, KindError};
-use crate::substitution::{self, Substitution};
-use crate::unify::{self, Error as UnifyError};
-use crate::unify_type::{self, new_skolem_scope, Error as UnifyTypeError};
+use crate::{
+    implicits,
+    kindcheck::{self, Error as KindCheckError, KindCheck, KindError},
+    substitution::{self, Substitution},
+    unify::{self, Error as UnifyError},
+    unify_type::{self, new_skolem_scope, Error as UnifyTypeError},
+};
 
 /// Type representing a single error when checking a type
 #[derive(Debug, PartialEq)]
@@ -1307,6 +1309,7 @@ impl<'a> Typecheck<'a> {
                 ref mut body,
                 ref mut flat_map_id,
             }) => {
+                let do_span = expr.span.subspan(0.into(), 2.into());
                 let flat_map_type = match flat_map_id
                     .as_mut()
                     .expect("flat_map inserted during renaming")
@@ -1316,7 +1319,7 @@ impl<'a> Typecheck<'a> {
                         Ok(x) => x,
                         Err(error) => {
                             self.error(
-                                id.span,
+                                do_span,
                                 crate::base::error::Help {
                                     error,
                                     help: Some(Help::UndefinedFlatMapInDo),
@@ -1334,11 +1337,11 @@ impl<'a> Typecheck<'a> {
                     Type::Function(ArgType::Implicit, ref arg_type, ref r) => {
                         let name = self.implicit_resolver.make_implicit_ident(arg_type);
                         *flat_map_id = Some(Box::new(pos::spanned(
-                            id.span,
+                            do_span,
                             Expr::App {
                                 func: flat_map_id.take().unwrap(),
                                 args: vec![pos::spanned(
-                                    id.span,
+                                    do_span,
                                     Expr::Ident(TypedIdent {
                                         name,
                                         typ: arg_type.clone(),
@@ -1352,12 +1355,12 @@ impl<'a> Typecheck<'a> {
                     _ => flat_map_type.clone(),
                 };
 
-                id.value.typ = self.subs.new_var();
+                let id_var = self.subs.new_var();
                 let arg1 = self
                     .type_cache
-                    .function(Some(id.value.typ.clone()), self.subs.new_var());
-
+                    .function(Some(id_var.clone()), self.subs.new_var());
                 let arg2 = self.subs.new_var();
+
                 let ret = expected_type
                     .cloned()
                     .unwrap_or_else(|| self.subs.new_var());
@@ -1365,18 +1368,13 @@ impl<'a> Typecheck<'a> {
                     .type_cache
                     .function(vec![arg1.clone(), arg2.clone()], ret.clone());
 
-                self.unify_span(expr.span, &flat_map_type, func_type);
+                self.unify_span(do_span, &flat_map_type, func_type);
 
-                let bound_type = self.typecheck(bound, &arg2);
+                self.typecheck(bound, &arg2);
 
-                self.unify_span(
-                    // Point to the `do` token
-                    expr.span.subspan(0.into(), 2.into()),
-                    &arg2,
-                    bound_type,
-                );
-
-                self.stack_var(id.value.name.clone(), id.value.typ.clone());
+                if let Some(ref mut id) = *id {
+                    self.typecheck_pattern(id, id_var);
+                }
 
                 let body_type = self.typecheck(body, &ret);
 
diff --git a/check/src/unify_type.rs b/check/src/unify_type.rs
index a5af8d0e..d50f2e49 100644
--- a/check/src/unify_type.rs
+++ b/check/src/unify_type.rs
@@ -1,22 +1,19 @@
 use std::{borrow::Cow, fmt, mem};
 
-use crate::base::{
-    error::Errors,
-    fnv::FnvMap,
-    kind::ArcKind,
-    merge,
-    resolve::{self, Error as ResolveError},
-    symbol::{Symbol, SymbolRef},
-    types::{
-        self, walk_move_type_opt, AppVec, ArcType, ArgType, BuiltinType, Field, Filter, Generic,
-        Skolem, Type, TypeCache, TypeEnv, TypeFormatter, TypeVariable,
-    },
+use crate::base::error::Errors;
+use crate::base::fnv::FnvMap;
+use crate::base::kind::ArcKind;
+use crate::base::merge;
+use crate::base::resolve::{self, Error as ResolveError};
+use crate::base::symbol::{Symbol, SymbolRef};
+use crate::base::types::{
+    self, AppVec, ArcType, ArgType, BuiltinType, Field, Filter, Generic, Skolem, Type, TypeCache,
+    TypeEnv, TypeFormatter, TypeVariable,
 };
 
-use crate::{
-    substitution::{Substitutable, Substitution, Variable, VariableFactory},
-    unify::{self, Error as UnifyError, GenericVariant, Unifiable, Unifier},
-};
+use crate::substitution::{Substitutable, Substitution, Variable, VariableFactory};
+use crate::unify;
+use crate::unify::{Error as UnifyError, GenericVariant, Unifiable, Unifier};
 
 use smallvec::SmallVec;
 
@@ -1179,29 +1176,6 @@ pub fn subsumes(
     }
 }
 
-pub fn subsumes_no_subst(
-    state: State,
-    l: &ArcType,
-    r: &ArcType,
-) -> Result<ArcType, (ArcType, Errors<Error<Symbol>>)> {
-    debug!("Subsume {} <=> {}", l, r);
-    let mut unifier = UnifierState {
-        unifier: Subsume {
-            subs: state.subs,
-            errors: Errors::new(),
-            allow_returned_type_replacement: true,
-        },
-        state: state,
-    };
-
-    let typ = unifier.try_match(l, r);
-    if unifier.unifier.errors.has_errors() {
-        Err((typ.unwrap_or_else(|| l.clone()), unifier.unifier.errors))
-    } else {
-        Ok(typ.unwrap_or_else(|| l.clone()))
-    }
-}
-
 struct Subsume<'e> {
     subs: &'e Substitution<ArcType>,
     errors: Errors<Error<Symbol>>,
@@ -1365,139 +1339,6 @@ fn reconstruct_forall(
     Type::forall_with_vars(new_params, inner_type, Some(new_vars))
 }
 
-pub fn equal(state: State, l: &ArcType, r: &ArcType) -> bool {
-    trace!("Equal {} <=> {}", l, r);
-    let mut unifier = UnifierState {
-        unifier: Equal {
-            subs: state.subs,
-            equal: true,
-        },
-        state,
-    };
-
-    unifier.try_match(l, r);
-    unifier.unifier.equal
-}
-
-struct Equal<'e> {
-    subs: &'e Substitution<ArcType>,
-    equal: bool,
-}
-
-impl<'a, 'e> Unifier<State<'a>, ArcType> for UnifierState<'a, Equal<'e>> {
-    fn report_error(&mut self, error: UnifyError<ArcType, TypeError<Symbol>>) {
-        debug!("Equal: Error {}", error);
-        self.unifier.equal = false;
-    }
-
-    fn try_match_res(
-        &mut self,
-        l: &ArcType,
-        r: &ArcType,
-    ) -> Result<Option<ArcType>, UnifyError<ArcType, TypeError<Symbol>>> {
-        let subs = self.unifier.subs;
-        let l = subs.real(l);
-        let r = subs.real(r);
-        debug!("{} <=> {}", l, r);
-        l.zip_match(r, self)
-    }
-
-    fn error_type(&self) -> ArcType {
-        ArcType::error_type(&self.state)
-    }
-}
-
-pub fn smaller(state: State, new_type: &ArcType, old_type: &ArcType) -> Size {
-    trace!("smaller: {} < {}", new_type, old_type);
-    let mut unifier = UnifierState {
-        unifier: Smaller {
-            size: Size::Equal,
-            just_encountered_error: false,
-        },
-        state,
-    };
-
-    unifier.try_match(new_type, old_type);
-    let size = unifier.unifier.size;
-    trace!("smaller return: {:?}", size);
-    size
-}
-
-#[derive(PartialEq, Debug)]
-pub enum Size {
-    Smaller,
-    Equal,
-    Different,
-}
-
-struct Smaller {
-    size: Size,
-    just_encountered_error: bool,
-}
-
-impl<'a, 'e> Unifier<State<'a>, ArcType> for UnifierState<'a, Smaller> {
-    fn report_error(&mut self, error: UnifyError<ArcType, TypeError<Symbol>>) {
-        debug!("Smaller: Error {}", error);
-    }
-
-    fn try_match_res(
-        &mut self,
-        l: &ArcType,
-        r: &ArcType,
-    ) -> Result<Option<ArcType>, UnifyError<ArcType, TypeError<Symbol>>> {
-        if self.unifier.size == Size::Different {
-            return Ok(None);
-        }
-        trace!("{} <=> {}", l, r);
-
-        match (&**l, &**r) {
-            // zip_match substitutes variables so we need to intercept variables here and compare
-            // them manually
-            (Type::Variable(_), _) | (_, Type::Variable(_)) => {
-                if l != r {
-                    self.unifier.size = Size::Different;
-                }
-                Ok(None)
-            }
-
-            _ => match l.zip_match(r, self) {
-                Err(_) => {
-                    self.unifier.size = Size::Different;
-                    walk_move_type_opt(r, &mut |inner_type: &ArcType| {
-                        if inner_type == l {
-                            self.unifier.size = Size::Smaller
-                        }
-                        None
-                    });
-                    if self.unifier.size == Size::Different {
-                        self.unifier.just_encountered_error = true;
-                    }
-                    Ok(None)
-                }
-                result => {
-                    if self.unifier.just_encountered_error {
-                        self.unifier.just_encountered_error = false;
-                        // We ended up finding a mismatch inside this type so we need to try the check
-                        // again in case the only cause for the error is that we resolved an alias
-                        // which then mismatched
-                        walk_move_type_opt(r, &mut |inner_type: &ArcType| {
-                            if inner_type == l {
-                                self.unifier.size = Size::Smaller
-                            }
-                            None
-                        });
-                    }
-                    result
-                }
-            },
-        }
-    }
-
-    fn error_type(&self) -> ArcType {
-        ArcType::error_type(&self.state)
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/completion/src/lib.rs b/completion/src/lib.rs
index c3d1c402..e869f9b6 100644
--- a/completion/src/lib.rs
+++ b/completion/src/lib.rs
@@ -678,18 +678,16 @@ where
                 }
             }
             Expr::Do(ref do_expr) => {
-                let iter = once(Either::Left(&do_expr.id))
+                let iter = do_expr
+                    .id
+                    .as_ref()
+                    .map(Either::Left)
+                    .into_iter()
                     .chain(once(Either::Right(&do_expr.bound)))
                     .chain(once(Either::Right(&do_expr.body)));
                 match self.select_spanned(iter, |x| x.either(|i| i.span, |e| e.span)) {
                     (_, Some(either)) => match either {
-                        Either::Left(ident) => {
-                            self.found = MatchState::Found(Match::Ident(
-                                ident.span,
-                                &ident.value.name,
-                                ident.value.typ.clone(),
-                            ));
-                        }
+                        Either::Left(ident) => self.visit_pattern(ident),
                         Either::Right(expr) => self.visit_expr(expr),
                     },
                     _ => unreachable!(),
diff --git a/examples/24.glu b/examples/24.glu
index eb95bee7..446847db 100644
--- a/examples/24.glu
+++ b/examples/24.glu
@@ -135,9 +135,9 @@ do digits =
     wrap [a, b, c, d]
 
 let print_digits = for digits (\d ->
-        do _ = io.print " "
+        seq io.print " "
         io.print (show d))
-do _ = io.print "Four digits:" *> print_digits *> io.println ""
+seq io.print "Four digits:" *> print_digits *> io.println ""
 
 let guess_loop _ =
     do line = io.read_line
diff --git a/examples/http/server.glu b/examples/http/server.glu
index a9396718..cef4dd8a 100644
--- a/examples/http/server.glu
+++ b/examples/http/server.glu
@@ -60,17 +60,17 @@ let sum : Eff (HttpEffect r) Response =
     do body = get_request >>= array_body
     match string.from_utf8 body with
     | Err _ ->
-        do _ = write_response (string.as_bytes "Request contained invalid UTF-8")
+        seq write_response (string.as_bytes "Request contained invalid UTF-8")
         wrap { status = status.bad_request, .. http.response }
     | Ok string_body ->
         match de.deserialize de.deserializer string_body with
         | Ok int_array ->
             let int_array : Array Int = int_array
             let s = foldl (+) 0 int_array 
-            do _ = write_response (string.as_bytes (show s))
+            seq write_response (string.as_bytes (show s))
             wrap { status = status.ok, .. http.response }
         | Err err ->
-            do _ = write_response (string.as_bytes err)
+            seq write_response (string.as_bytes err)
             wrap { status = status.bad_request, .. http.response }
 
 
diff --git a/examples/lisp/lisp.glu b/examples/lisp/lisp.glu
index 22e22309..290ef99f 100644
--- a/examples/lisp/lisp.glu
+++ b/examples/lisp/lisp.glu
@@ -73,7 +73,7 @@ let { fold_m } = import! std.foldable
 let scope_state run : Eff (LispEffect r) a -> Eff (LispEffect r) a =
     do original = get
     do x = run
-    do _ = put original
+    seq put original
     wrap x
 
 let primitive name f : String -> _ -> Map String Expr = std_map.singleton name (Primitive f)
@@ -112,7 +112,7 @@ let define xs =
     | Cons (Atom name) (Cons value Nil) ->
         do state = get
         let new_state = std_map.insert name value state
-        do _ = put new_state
+        seq put new_state
         wrap value
     | Cons (List (Cons (Atom name) params)) body ->
         do closure = get
@@ -125,7 +125,7 @@ let define xs =
                 }
         let new_state = std_map.insert name function closure
 
-        do _ = put new_state
+        seq put new_state
 
         wrap function
     | _ -> throw "Unexpected parameters to define `define`"
@@ -141,7 +141,7 @@ let apply f xs : forall r . Expr -> List Expr -> Eff (LispEffect r) Expr =
     let add_args names values : List String -> _ =
         match (names, values) with
         | (Cons name names, Cons value values) ->
-            do _ = modify (\state -> std_map.insert name value state)
+            seq modify (\state -> std_map.insert name value state)
             add_args names values
         | (Nil, _) -> wrap ()
         | _ -> throw "Not enough arguments to function"
@@ -150,7 +150,7 @@ let apply f xs : forall r . Expr -> List Expr -> Eff (LispEffect r) Expr =
     | Primitive primitive -> primitive xs
     | Function function ->
         scope_state (
-            do _ = add_args function.params xs
+            seq add_args function.params xs
             eval_exprs function.body)
     | _ -> throw ("Can\'t call value: " <> show f)
 
diff --git a/examples/marshalling.rs b/examples/marshalling.rs
index e8718cba..692fe8e9 100644
--- a/examples/marshalling.rs
+++ b/examples/marshalling.rs
@@ -198,7 +198,7 @@ fn marshal_generic() -> Result<()> {
         let either: forall r . Either String r = Left "hello rust!"
 
         // we can pass the generic Either to the Rust function without an issue
-        do _ = 
+        seq 
             match flip either with
             | Left _ -> error "unreachable!"
             | Right val -> io.println ("Right is: " <> val)
diff --git a/format/src/pretty_print.rs b/format/src/pretty_print.rs
index b0cfef96..4223f15d 100644
--- a/format/src/pretty_print.rs
+++ b/format/src/pretty_print.rs
@@ -473,11 +473,16 @@ where
                 ..
             }) => chain![arena;
                 chain![arena;
-                    "do",
-                    self.space_before(id.span.start()),
-                    id.value.name.as_ref(),
-                    self.space_after(id.span.end()),
-                    "=",
+                    match id {
+                        Some(pattern) => chain![arena;
+                            "do",
+                            self.space_before(pattern.span.start()),
+                            self.pretty_pattern(pattern),
+                            self.space_after(pattern.span.end()),
+                            "="
+                        ],
+                        None => arena.text("seq"),
+                    },
                     self.hang(arena.nil(), bound).group()
                 ].group(),
                 self.pretty_expr_(bound.span.end(), body)
diff --git a/parser/src/grammar.lalrpop b/parser/src/grammar.lalrpop
index 42951cfd..22ab216c 100644
--- a/parser/src/grammar.lalrpop
+++ b/parser/src/grammar.lalrpop
@@ -2,7 +2,7 @@ use crate::itertools::{Either, Itertools};
 
 use crate::base::{
     ast::{Alternative, Argument, Array, AstType, Do, Expr, ExprField, Lambda, Literal, Pattern,
-    PatternField, SpannedExpr, SpannedIdent, TypeBinding, TypedIdent, ValueBinding, ValueBindings},
+    PatternField, SpannedExpr, SpannedIdent, SpannedPattern, TypeBinding, TypedIdent, ValueBinding, ValueBindings},
     kind::{ArcKind, Kind},
     pos::{self, BytePos, Spanned},
     types::{Alias, AliasData, ArcType, ArgType, BuiltinType, Field, Generic, Type, TypeCache},
@@ -42,6 +42,7 @@ extern {
         "in" => Token::In,
         "let" => Token::Let,
         "do" => Token::Do,
+        "seq" => Token::Seq,
         "match" => Token::Match,
         "then" => Token::Then,
         "type" => Token::Type,
@@ -753,8 +754,12 @@ Expr: Expr<Id> = {
         Expr::TypeBindings(bindings, Box::new(body))
     },
 
-    "do" <id: SpannedIdent> "=" <bound: SpExpr> <body: InExpr> => {
-        Expr::Do(Do { id, bound: Box::new(bound), body: Box::new(body), flat_map_id: None })
+    "do" <bind: DoBinding> <body: InExpr> => {
+        Expr::Do(Do { id: Some(bind.0), bound: Box::new(bind.1), body: Box::new(body), flat_map_id: None })
+    },
+
+    "seq" <bound: SpExpr> <body: InExpr> => {
+        Expr::Do(Do { id: None, bound: Box::new(bound), body: Box::new(body), flat_map_id: None })
     },
 
     BlockExpr,
@@ -765,6 +770,10 @@ Expr: Expr<Id> = {
     }
 };
 
+DoBinding: (SpannedPattern<Id>, SpannedExpr<Id>) = {
+    <id: Sp<Pattern>> "=" <bound: SpExpr> => (id, bound),
+};
+
 BlockExpr: Expr<Id> = {
     "block open" <exprs: (<SpExpr> "block separator")*> <last: SpExpr> "block close" => {
         let mut exprs = exprs;
diff --git a/parser/src/layout.rs b/parser/src/layout.rs
index a410cbfd..a5918505 100644
--- a/parser/src/layout.rs
+++ b/parser/src/layout.rs
@@ -500,7 +500,7 @@ where
                 Token::Rec => Some(Context::Rec),
                 Token::Type => Some(Context::Type),
                 Token::Let => Some(Context::Let),
-                Token::Do => Some(Context::Let),
+                Token::Do | Token::Seq => Some(Context::Let),
                 Token::If => Some(Context::If),
                 Token::Match => Some(Context::Expr),
                 Token::Lambda => Some(Context::Lambda),
diff --git a/parser/src/token.rs b/parser/src/token.rs
index 0ec0a48d..1426c9a5 100644
--- a/parser/src/token.rs
+++ b/parser/src/token.rs
@@ -28,6 +28,7 @@ pub enum Token<'input> {
     In,
     Let,
     Do,
+    Seq,
     Match,
     Then,
     Type,
@@ -82,6 +83,7 @@ impl<'input> fmt::Display for Token<'input> {
             In => "In",
             Let => "Let",
             Do => "Do",
+            Seq => "Seq",
             Match => "Match",
             Then => "Then",
             Type => "Type",
@@ -497,7 +499,7 @@ impl<'input> Tokenizer<'input> {
                 let (end, float) = self.take_while(start, is_digit);
                 match self.lookahead {
                     Some((_, ch)) if is_ident_start(ch) => {
-                        return self.error(end, UnexpectedChar(ch))
+                        return self.error(end, UnexpectedChar(ch));
                     }
                     _ => (start, end, Token::FloatLiteral(float.parse().unwrap())),
                 }
@@ -509,7 +511,7 @@ impl<'input> Tokenizer<'input> {
                 match int {
                     "0" | "-0" => match self.lookahead {
                         Some((_, ch)) if is_ident_start(ch) => {
-                            return self.error(end, UnexpectedChar(ch))
+                            return self.error(end, UnexpectedChar(ch));
                         }
                         _ => {
                             if hex.is_empty() {
@@ -530,7 +532,7 @@ impl<'input> Tokenizer<'input> {
                 let end = self.next_loc();
                 match self.lookahead {
                     Some((pos, ch)) if is_ident_start(ch) => {
-                        return self.error(pos, UnexpectedChar(ch))
+                        return self.error(pos, UnexpectedChar(ch));
                     }
                     _ => {
                         if let Ok(val) = int.parse() {
@@ -574,6 +576,7 @@ impl<'input> Tokenizer<'input> {
             "in" => Token::In,
             "let" => Token::Let,
             "do" => Token::Do,
+            "seq" => Token::Seq,
             "match" => Token::Match,
             "then" => Token::Then,
             "type" => Token::Type,
diff --git a/repl/src/repl.glu b/repl/src/repl.glu
index f375d453..b7e8d77f 100644
--- a/repl/src/repl.glu
+++ b/repl/src/repl.glu
@@ -209,7 +209,7 @@ let cmd_parser : Parser { cmd : String, arg : String } =
     let word = recognize (skip_many1 letter)
     let arg_parser = recognize (skip_many1 any)
 
-    do _ = token ':'
+    seq token ':'
     do cmd = word
     do arg = (spaces *> arg_parser) <|> wrap ""
     wrap { cmd, arg }
@@ -251,11 +251,11 @@ let loop _ : () -> Eff (ReplEffect r) () =
         match continue with
         | Continue -> loop ()
         | Quit ->
-            do _ = lift <| rustyline.save_history repl.editor
+            seq lift <| rustyline.save_history repl.editor
             wrap ()
 
 let run settings : Settings -> IO () =
-    do _ = io.println "gluon (:h for help, :q to quit)"
+    seq io.println "gluon (:h for help, :q to quit)"
     do editor = rustyline.new_editor ()
     do cpu_pool = repl_prim.new_cpu_pool 1
     let repl = { commands, editor, cpu_pool }
diff --git a/std/parser.glu b/std/parser.glu
index b4f96ec1..fd9efa22 100644
--- a/std/parser.glu
+++ b/std/parser.glu
@@ -207,7 +207,7 @@ let skip_many p : Parser a -> Parser () =
     skip_many1 p <|> wrap ()
 /// Parses with `p` one or more times, ignoring the result of the parser
 let skip_many1 p : Parser a -> Parser () =
-    do _ = p
+    seq p
     skip_many p
 in
 /// Parses one of the characters of `s`
diff --git a/vm/src/core/mod.rs b/vm/src/core/mod.rs
index 0c3496e5..a078f88c 100644
--- a/vm/src/core/mod.rs
+++ b/vm/src/core/mod.rs
@@ -38,24 +38,20 @@ pub mod optimize;
 #[cfg(feature = "test")]
 mod pretty;
 
-use std::borrow::Cow;
-use std::collections::HashMap;
-use std::fmt;
-use std::iter::once;
-use std::mem;
+use std::{borrow::Cow, collections::HashMap, fmt, iter::once, mem};
 
 use itertools::Itertools;
 
-use self::typed_arena::Arena;
+use self::{smallvec::SmallVec, typed_arena::Arena};
 
-use self::smallvec::SmallVec;
-
-use crate::base::ast::{self, Literal, SpannedExpr, SpannedPattern, Typed, TypedIdent};
-use crate::base::fnv::{FnvMap, FnvSet};
-use crate::base::pos::{spanned, BytePos, Span};
-use crate::base::resolve::remove_aliases_cow;
-use crate::base::symbol::Symbol;
-use crate::base::types::{arg_iter, ArcType, PrimitiveEnv, Type, TypeEnv};
+use crate::base::{
+    ast::{self, Literal, SpannedExpr, SpannedPattern, Typed, TypedIdent},
+    fnv::{FnvMap, FnvSet},
+    pos::{spanned, BytePos, Span, Spanned},
+    resolve::remove_aliases_cow,
+    symbol::Symbol,
+    types::{arg_iter, ArcType, PrimitiveEnv, Type, TypeEnv},
+};
 
 #[derive(Clone, Debug, PartialEq)]
 pub struct Closure<'a> {
@@ -567,11 +563,42 @@ impl<'a, 'e> Translator<'a, 'e> {
                 let bound_ident =
                     binder.bind(self.translate_alloc(bound), bound.env_type_of(&self.env));
 
+                let do_id = id.as_ref().map_or_else(
+                    || self.dummy_symbol.clone(),
+                    |pat| match pat.value {
+                        ast::Pattern::Ident(ref id) => id.clone(),
+                        _ => self.dummy_symbol.clone(),
+                    },
+                );
+
+                let core_body = self.translate_alloc(body);
+                let core_body = match id.as_ref() {
+                    Some(Spanned {
+                        value: ast::Pattern::Ident(_),
+                        ..
+                    })
+                    | None => core_body,
+                    Some(pat) => {
+                        let id_expr = self
+                            .allocator
+                            .arena
+                            .alloc(Expr::Ident(do_id.clone(), pat.span));
+                        let e = PatternTranslator(self).translate_top(
+                            id_expr,
+                            &[Equation {
+                                patterns: vec![&pat],
+                                result: core_body,
+                            }],
+                        );
+                        self.allocator.arena.alloc(e)
+                    }
+                };
+
                 let lambda = self.new_lambda(
                     expr.span.start(),
-                    id.value.clone(),
-                    vec![id.value.clone()],
-                    self.translate_alloc(body),
+                    do_id.clone(),
+                    vec![do_id],
+                    core_body,
                     body.span,
                 );
 


Test patch:
diff --git a/check/tests/implicits.rs b/check/tests/implicits.rs
index 217797dd..2e761912 100644
--- a/check/tests/implicits.rs
+++ b/check/tests/implicits.rs
@@ -596,7 +596,7 @@ let monad : Monad List =
 
 let fold_m monad f z w : forall a b m t . [Foldable t] -> Monad m -> (a -> b -> m a) -> a -> t b -> () = ()
 
-let fold_m2 ?x : [Foldable t] -> _ -> _ -> t b -> _ = fold_m monad
+let fold_m2 ?x : [Foldable t] -> _ = fold_m monad
 
 let f x : List Int -> _ = fold_m2 (\a b -> Cons a Nil) x
 f
@@ -872,86 +872,3 @@ f 1
 
     assert!(result.is_ok(), "{}", result.unwrap_err());
 }
-
-test_check_err! {
-break_infinte_implicit_resolve_early,
-    r#"
-#[implicit]
-type Implicit a = { f : a -> String } 
-
-type Test a = { x : a }
-
-let any x = any x
-
-let test : [Implicit a] -> Implicit (Test a) = any ()
-
-let f : [Implicit a] -> a -> () = any ()
-
-f (any ())
-"#,
-TypeError::UnableToResolveImplicit(ImplicitError {
-    kind: ImplicitErrorKind::LoopInImplicitResolution(..),
-    ..
-})
-}
-
-test_check! {
-one_binding_used_twice,
-r#"
-#[implicit]
-type Test a = | Test a
-
-type Pair a b = { x : a, y : b }
-
-let any x = any x
-
-let pair_test: [Test a] -> [Test b] -> Test (Pair a b) = any ()
-let int_test: Test Int = Test 1
-
-let test x : [Test a] -> a -> () = ()
-
-test { x = 1, y = 2 }
-"#,
-"()"
-}
-
-test_check! {
-one_binding_nested_1,
-r#"
-#[implicit]
-type Test a = | Test a
-
-type Value a = { x : a }
-
-let any x = any x
-
-let value_test: [Test a] -> Test (Value a) = any ()
-let int_test: Test Int = Test 1
-
-let test x : [Test a] -> a -> () = ()
-
-test { x = { x = 1 } }
-"#,
-"()"
-}
-
-test_check! {
-one_binding_nested_2,
-r#"
-#[implicit]
-type Test a = | Test a
-
-type Value a = { x : a }
-
-let any x = any x
-
-let value_test: [Test a] -> Test (Value a) = any ()
-let value_test: [Test a] -> Test (Array a) = any ()
-let int_test: Test Int = Test 1
-
-let test x : [Test a] -> a -> () = ()
-
-test { x = [{ x = 1 }] }
-"#,
-"()"
-}
diff --git a/check/tests/pass.rs b/check/tests/pass.rs
index fc45bdbe..0ef34bb0 100644
--- a/check/tests/pass.rs
+++ b/check/tests/pass.rs
@@ -744,6 +744,21 @@ test (x #Int+ 2)
     assert_req!(result.map(support::close_record), expected);
 }
 
+test_check! {
+do_expression_bind_scope,
+r#"
+type Test a = { x : a }
+let flat_map f x : (a -> Test b) -> Test a -> Test b = f x.x
+let test x : a -> Test a = { x }
+
+let f y : () -> Test Int =
+    do y = test 1
+    test (y #Int+ 2)
+f
+"#,
+"() -> test.Test Int"
+}
+
 #[test]
 fn eq_unresolved_constraint_bug() {
     let _ = env_logger::try_init();
diff --git a/check/tests/support/mod.rs b/check/tests/support/mod.rs
index fd7a0378..d5ba7dcf 100644
--- a/check/tests/support/mod.rs
+++ b/check/tests/support/mod.rs
@@ -6,10 +6,9 @@ extern crate codespan;
 use crate::base::{
     self,
     ast::{DisplayEnv, Expr, IdentEnv, SpannedExpr},
-    error::{Errors, InFile},
+    error::InFile,
     kind::{ArcKind, Kind, KindEnv},
     metadata::{Metadata, MetadataEnv},
-    pos::{BytePos, Spanned},
     symbol::{Symbol, SymbolModule, SymbolRef, Symbols},
     types::{self, Alias, ArcType, Field, Generic, PrimitiveEnv, Type, TypeCache, TypeEnv},
 };
@@ -21,7 +20,7 @@ use crate::check::{
     typecheck::{self, Typecheck},
 };
 
-use std::{cell::RefCell, fmt, marker::PhantomData, rc::Rc};
+use std::{cell::RefCell, marker::PhantomData, rc::Rc};
 
 quick_error! {
     /// Representation of all possible errors that can occur when interacting with the `vm` crate
@@ -170,15 +169,6 @@ where
     }
 }
 
-pub(crate) fn in_file_error<E>(text: &str, errors: Errors<Spanned<E, BytePos>>) -> InFile<E>
-where
-    E: fmt::Display,
-{
-    let mut source = codespan::CodeMap::new();
-    source.add_filemap("test".into(), text.into());
-    InFile::new(source, errors)
-}
-
 pub fn typecheck_expr_expected(
     text: &str,
     expected: Option<&ArcType>,
@@ -186,7 +176,9 @@ pub fn typecheck_expr_expected(
     let mut expr = match parse_new(text) {
         Ok(expr) => expr,
         Err((expr, err)) => {
-            let err = in_file_error(text, err);
+            let mut source = codespan::CodeMap::new();
+            source.add_filemap("test".into(), text.into());
+            let err = InFile::new(source, err);
             return (expr.unwrap_or_else(|| panic!("{}", err)), Err(err.into()));
         }
     };
@@ -212,7 +204,14 @@ pub fn typecheck_expr_expected(
 
     let result = tc.typecheck_expr_expected(&mut expr, expected);
 
-    (expr, result.map_err(|err| in_file_error(text, err).into()))
+    (
+        expr,
+        result.map_err(|err| {
+            let mut source = codespan::CodeMap::new();
+            source.add_filemap("test".into(), text.into());
+            InFile::new(source, err).into()
+        }),
+    )
 }
 
 pub fn typecheck_expr(text: &str) -> (SpannedExpr<Symbol>, Result<ArcType, Error>) {
@@ -253,7 +252,14 @@ pub fn typecheck_partial_expr(
 
     let result = tc.typecheck_expr(&mut expr);
 
-    (expr, result.map_err(|err| in_file_error(text, err)))
+    (
+        expr,
+        result.map_err(|err| {
+            let mut source = codespan::CodeMap::new();
+            source.add_filemap("test".into(), text.into());
+            InFile::new(source, err)
+        }),
+    )
 }
 
 #[allow(dead_code)]
@@ -396,16 +402,13 @@ macro_rules! test_check_err {
             let _ = env_logger::try_init();
             let text = $source;
             let result = support::typecheck(text);
-            assert_err!([$source] result, $($id),+);
+            assert_err!(result, $($id),+);
         }
     };
 }
 
 macro_rules! assert_err {
-    ($e: expr, $($id: pat),+) => {
-        assert_err!([""] $e, $($id),+)
-    };
-    ([$text: expr] $e: expr, $($id: pat),+) => {{
+    ($e: expr, $($id: pat),+) => {{
         #[allow(unused_imports)]
         use crate::check::{
             typecheck::TypeError::*,
@@ -425,23 +428,8 @@ macro_rules! assert_err {
                 $(
                 match iter.next() {
                     Some(&crate::base::pos::Spanned { value: crate::base::error::Help { error: $id, .. }, .. }) => (),
-                    _ => {
-                        if $text.is_empty() {
-                            assert!(
-                                false,
-                                "Found errors:\n{}\nbut expected {}",
-                                errors,
-                                stringify!($id)
-                            )
-                        } else {
-                            assert!(
-                                false,
-                                "Found errors:\n{}\nbut expected {}",
-                                crate::support::in_file_error($text, errors),
-                                stringify!($id)
-                            )
-                        }
-                    }
+                    _ => assert!(false, "Found errors:\n{}\nbut expected {}",
+                                        errors, stringify!($id)),
                 }
                 )+
                 assert!(iter.count() == 0, "Found more errors than expected\n{}", errors);
diff --git a/parser/tests/basic.rs b/parser/tests/basic.rs
index 54f53b77..a302eb83 100644
--- a/parser/tests/basic.rs
+++ b/parser/tests/basic.rs
@@ -821,7 +821,7 @@ fn do_in_parens() {
     let _ = ::env_logger::try_init();
     let text = r"
         scope_state (
-            do _ = add_args
+            seq add_args
             eval_exprs
         )
     ";
diff --git a/tests/io.rs b/tests/io.rs
index 6dcee004..f25f2140 100644
--- a/tests/io.rs
+++ b/tests/io.rs
@@ -79,7 +79,7 @@ fn write_and_flush_file() {
     assert_eq!(content, vec![1, 2, 3, 4]);
 }
 
-test_expr!{ no_io_eval,
+test_expr! { no_io_eval,
 r#"
 let { error } = import! std.prim
 let io = import! std.io
@@ -171,7 +171,8 @@ fn spawn_on_twice() {
             Compiler::new()
                 .run_io(true)
                 .run_expr_async::<IO<String>>(&vm, "<top>", text),
-        ).unwrap_or_else(|err| panic!("{}", err));
+        )
+        .unwrap_or_else(|err| panic!("{}", err));
     match result {
         IO::Value(result) => {
             assert_eq!(result, "abc");
@@ -184,7 +185,8 @@ fn spawn_on_twice() {
             Compiler::new()
                 .run_io(true)
                 .run_expr_async::<IO<String>>(&vm, "<top>", text),
-        ).unwrap_or_else(|err| panic!("{}", err));
+        )
+        .unwrap_or_else(|err| panic!("{}", err));
     match result {
         IO::Value(result) => {
             assert_eq!(result, "abc");
@@ -204,7 +206,7 @@ fn spawn_on_runexpr() {
         do child = thread.new_thread ()
         do action = thread.spawn_on child (\_ -> io.run_expr "123")
         do x = action
-        do _ = io.println x.value
+        seq io.println x.value
         wrap x.value
     "#;
 
@@ -215,7 +217,8 @@ fn spawn_on_runexpr() {
             Compiler::new()
                 .run_io(true)
                 .run_expr_async::<IO<String>>(&vm, "<top>", text),
-        ).unwrap_or_else(|err| panic!("{}", err));
+        )
+        .unwrap_or_else(|err| panic!("{}", err));
     match result {
         IO::Value(result) => {
             assert_eq!(result, "123");
@@ -241,8 +244,8 @@ fn spawn_on_do_action_twice() {
         let action = thread.spawn_on child (\_ ->
                 counter <- (load counter + 1)
                 wrap ())
-        do _ = join action
-        do _ = join action
+        seq join action
+        seq join action
         wrap (load counter)
     "#;
 
@@ -253,7 +256,8 @@ fn spawn_on_do_action_twice() {
             Compiler::new()
                 .run_io(true)
                 .run_expr_async::<IO<i32>>(&vm, "<top>", text),
-        ).unwrap_or_else(|err| panic!("{}", err));
+        )
+        .unwrap_or_else(|err| panic!("{}", err));
     assert_eq!(result, IO::Value(2));
 }
 
@@ -273,8 +277,8 @@ fn spawn_on_force_action_twice() {
         do action = thread.spawn_on child (\_ ->
                 counter <- (load counter + 1)
                 wrap ())
-        do _ = action
-        do _ = action
+        seq action
+        seq action
         wrap (load counter)
     "#;
 
@@ -285,7 +289,8 @@ fn spawn_on_force_action_twice() {
             Compiler::new()
                 .run_io(true)
                 .run_expr_async::<IO<i32>>(&vm, "<top>", text),
-        ).unwrap_or_else(|err| panic!("{}", err));
+        )
+        .unwrap_or_else(|err| panic!("{}", err));
     assert_eq!(result, IO::Value(1));
 }
 
@@ -316,7 +321,8 @@ fn spawn_on_runexpr_in_catch() {
             Compiler::new()
                 .run_io(true)
                 .run_expr_async::<IO<String>>(&vm, "<top>", text),
-        ).unwrap_or_else(|err| panic!("{}", err));
+        )
+        .unwrap_or_else(|err| panic!("{}", err));
     match result {
         IO::Value(result) => {
             assert_eq!(result, "123");
@@ -329,7 +335,8 @@ fn spawn_on_runexpr_in_catch() {
             Compiler::new()
                 .run_io(true)
                 .run_expr_async::<IO<String>>(&vm, "<top>", text),
-        ).unwrap_or_else(|err| panic!("{}", err));
+        )
+        .unwrap_or_else(|err| panic!("{}", err));
     match result {
         IO::Value(result) => {
             assert_eq!(result, "123");
diff --git a/tests/pass/state.glu b/tests/pass/state.glu
index 743c57b6..11a4b75b 100644
--- a/tests/pass/state.glu
+++ b/tests/pass/state.glu
@@ -3,12 +3,20 @@ let { (<|) } = import! std.function
 let { Test, run, assert, assert_eq, test, group, ? } = import! std.test
 let int = import! std.int
 let state @ { State, put, get, modify, runState, evalState, execState, ? } = import! std.state
-let { Applicative, (*>), ? } = import! std.applicative
+let { Applicative, wrap, (*>), ? } = import! std.applicative
 
 group "state" [
     test "modify execState" <| \_ -> (assert_eq (execState (modify (\x -> x + 2) *> modify (\x -> x * 4)) 0) 8),
     test "modify evalState" <| \_ -> (assert_eq (evalState (modify (\x -> x + 2) *> get) 0) 2),
     test "put get evalState" <| \_ -> (assert_eq (evalState (put "hello" *> get) "") "hello"),
     test "put get runState" <| \_ -> (assert_eq (runState (put "hello" *> get) "").value "hello"),
+
+    /* FIXME
+    test "record unpack runState" <| \_ ->
+        let m : State { x : Int } Int =
+            do { x } = get
+            wrap x
+        assert_eq (evalState m { x = 1 }) 1,
+    */
 ]
 
diff --git a/tests/pass/thread.glu b/tests/pass/thread.glu
index 649c96e4..ea980faa 100644
--- a/tests/pass/thread.glu
+++ b/tests/pass/thread.glu
@@ -29,11 +29,11 @@ resume thread
 
 let tests : TestEff r () =
     assert_eq (recv receiver) (Ok 0) *> (
-            do _ = assert_eq (recv receiver) (Err ())
+            seq assert_eq (recv receiver) (Err ())
             resume thread
             assert_eq (recv receiver) (Ok 1)
         ) *> (
-            do _ = assert_eq (recv receiver) (Err ())
+            seq assert_eq (recv receiver) (Err ())
             assert_any_err (resume thread) (Err "Any error message here")
         )