fromType :: T.Canonical -> Expression () -> Expression ()
fromType tipe x =
    case tipe of
      T.Aliased _ args t ->
          fromType (T.dealias args t) x

      T.Lambda _ _
          | numArgs > 1 && numArgs < 10 ->
              func (ref ('A':show numArgs) `call` (x:values))
          | otherwise ->
              func (foldl (<|) x values)
          where
            ts = T.collectLambdas tipe
            numArgs = length ts - 1
            args = map (\n -> '_' : show n) [0..]
            values = zipWith toType (init ts) (map ref args)
            func body =
                function (take numArgs args)
                    [ VarDeclStmt () [VarDecl () (var "_r") (Just body)]
                    , ReturnStmt () (Just (fromType (last ts) (ref "_r")))
                    ]

      T.Var _ ->
          error "type variables should not be allowed through outputs"

      T.Type (Var.Canonical Var.BuiltIn name)
          | name `elem` ["Int","Float","Bool","String"] ->
              x

      T.Type name
          | Var.isJson name -> x
          | Var.isTuple name -> ArrayLit () []
          | otherwise -> error "bad type got to an output"

      T.App f args ->
          case f : args of
            T.Type name : [t]
                | Var.isMaybe name ->
                    CondExpr ()
                        (equal (DotRef () x (var "ctor")) (StringLit () "Nothing"))
                        (NullLit ())
                        (fromType t (DotRef () x (var "_0")))

                | Var.isArray name ->
                    DotRef () (_Array "toJSArray" <| x) (var "map") <| fromTypeFunction t

                | Var.isList name ->
                    DotRef () (_List "toArray" <| x) (var "map") <| fromTypeFunction t

            T.Type name : ts
                | Var.isTuple name ->
                    let convert n t = fromType t $ DotRef () x $ var ('_':show n)
                    in  ArrayLit () $ zipWith convert [0..] ts

            _ -> error "bad ADT got to an output"

      T.Record _ (Just _) ->
          error "bad record got to an output"

      T.Record fields Nothing ->
          ObjectLit () keys
        where
          keys = map convert fields
          convert (f,t) =
              (PropId () (var f), fromType t (DotRef () x (var f)))
fromType :: T.Canonical -> Expression () -> Expression ()
fromType tipe x =
    case tipe of
      T.Aliased _ args t ->
          fromType (T.dealias args t) x

      T.Lambda _ _
          | numArgs > 1 && numArgs < 10 ->
              func (ref ('A':show numArgs) `call` (x:values))
          | otherwise ->
              func (foldl (<|) x values)
          where
            ts = T.collectLambdas tipe
            numArgs = length ts - 1
            args = map (\n -> '_' : show n) [0..]
            values = zipWith toType (init ts) (map ref args)
            func body =
                function (take numArgs args)
                    [ VarDeclStmt () [VarDecl () (var "_r") (Just body)]
                    , ReturnStmt () (Just (fromType (last ts) (ref "_r")))
                    ]

      T.Var _ ->
          error "type variables should not be allowed through outputs"

      T.Type (Var.Canonical Var.BuiltIn name)
          | name `elem` ["Int","Float","Bool","String"] ->
              x

      T.Type name
          | Var.isJson name -> x
          | Var.isTuple name -> ArrayLit () []
          | otherwise -> error "bad type got to an output"

      T.App f args ->
          case f : args of
            T.Type name : [t]
                | Var.isMaybe name ->
                    CondExpr ()
                        (equal (DotRef () x (var "ctor")) (StringLit () "Nothing"))
                        (NullLit ())
                        (fromType t (DotRef () x (var "_0")))

                | Var.isArray name ->
                    DotRef () (_Array "toJSArray" <| x) (var "map") <| fromTypeFunction t

                | Var.isList name ->
                    DotRef () (_List "toArray" <| x) (var "map") <| fromTypeFunction t

            T.Type name : ts
                | Var.isTuple name ->
                    let convert n t = fromType t $ DotRef () x $ var ('_':show n)
                    in  ArrayLit () $ zipWith convert [0..] ts

            _ -> error "bad ADT got to an output"

      T.Record _ (Just _) ->
          error "bad record got to an output"

      T.Record fields Nothing ->
          ObjectLit () keys
        where
          keys = map convert fields
          convert (f,t) =
              (PropId () (var f), fromType t (DotRef () x (var f)))
fromType tipe x =
    case tipe of
      T.Aliased _ args t ->
          fromType (T.dealias args t) x

      T.Lambda _ _
          | numArgs > 1 && numArgs < 10 ->
              func (ref ('A':show numArgs) `call` (x:values))
          | otherwise ->
              func (foldl (<|) x values)
          where
            ts = T.collectLambdas tipe
            numArgs = length ts - 1
            args = map (\n -> '_' : show n) [0..]
            values = zipWith toType (init ts) (map ref args)
            func body =
                function (take numArgs args)
                    [ VarDeclStmt () [VarDecl () (var "_r") (Just body)]
                    , ReturnStmt () (Just (fromType (last ts) (ref "_r")))
                    ]

      T.Var _ ->
          error "type variables should not be allowed through outputs"

      T.Type (Var.Canonical Var.BuiltIn name)
          | name `elem` ["Int","Float","Bool","String"] ->
              x

      T.Type name
          | Var.isJson name -> x
          | Var.isTuple name -> ArrayLit () []
          | otherwise -> error "bad type got to an output"

      T.App f args ->
          case f : args of
            T.Type name : [t]
                | Var.isMaybe name ->
                    CondExpr ()
                        (equal (DotRef () x (var "ctor")) (StringLit () "Nothing"))
                        (NullLit ())
                        (fromType t (DotRef () x (var "_0")))

                | Var.isArray name ->
                    DotRef () (_Array "toJSArray" <| x) (var "map") <| fromTypeFunction t

                | Var.isList name ->
                    DotRef () (_List "toArray" <| x) (var "map") <| fromTypeFunction t

            T.Type name : ts
                | Var.isTuple name ->
                    let convert n t = fromType t $ DotRef () x $ var ('_':show n)
                    in  ArrayLit () $ zipWith convert [0..] ts

            _ -> error "bad ADT got to an output"

      T.Record _ (Just _) ->
          error "bad record got to an output"

      T.Record fields Nothing ->
          ObjectLit () keys
        where
          keys = map convert fields
          convert (f,t) =
              (PropId () (var f), fromType t (DotRef () x (var f)))