# Jasper: a Crystal-like programming language, but mostly a playground for theories

**URL:** <https://forum.crystal-lang.org/t/jasper-a-crystal-like-programming-language-but-mostly-a-playground-for-theories/9206>\
**Category:** Offtopic\
**Created:** [October 11, 2026, 2:46am UTC](https://forum.crystal-lang.org/t/jasper-a-crystal-like-programming-language-but-mostly-a-playground-for-theories/9206 "2026-10-11T02:46:44Z")\
**Posts on this page:** 1\
**Showing post:** 4

<div class="post-metadata">

**Author:** ![luislavena](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/luislavena/32/572_2.png) [@luislavena](https://forum.crystal-lang.org/u/luislavena)\
**Post date:** [October 14, 2026, 3:49pm UTC](https://forum.crystal-lang.org/t/jasper-a-crystal-like-programming-language-but-mostly-a-playground-for-theories/9206/4 "2026-10-14T15:49:59Z")

</div>

I would say it started with a compatibility desire, but started to address things differently and drift apart from it.

1. Types are required everywhere, except for local variables and `initialize` signature (assumes Void)

```crystal
def foo(bar)
end

foo(10)

```

```auto
$ bin/jasper run 21.jasper
21.jasper:1:12: error: expected type annotation
  def foo(bar)
             ^
21.jasper:1:13: error: expected type annotation
  def foo(bar)
              ^

```

Even constants require types (which Crystal 1.22 recently introduced)

2. All code is type checked, independently if is used (walked) or not:

```crystal
class Foo
end

class Bar < Foo
end

def foo(x : Foo) : Int32
  1
end

def foo(x : Bat) : Int32
  2
end

foo(Bar.new)

```

The above compiles fine in Crystal, but fails to compile in Jasper:

```auto
$ bin/jasper run 20.jasper
20.jasper:11:9: error: unknown type `Bat`
  def foo(x : Bat) : Int32
          ^^^^^^^

```

(even if you don’t do `foo(Bar.new)`)

Note that while everything is type-checked, it does not mean all the code is generated. A walker checks what paths of the code can be invoked and then only generates IR for those.

3. There is no argless, magical `super` like Ruby/Crystal

Right now if you have a Parent/Child hierarchy and their `initialize` has multiple arguments, doing `super` with no arguments will automatically forward the ones of that child initialize.

That does not compile in Jasper: it requires explicit calling instead of assumptions:

```crystal
class Parent
  def initialize(@value : Int32)
  end

  def value : Int32
    @value
  end
end

class Child < Parent
  def initialize(value : Int32, extra : String)
    super
  end
end

```

```auto
$ bin/jasper run 23.jasper
23.jasper:12:5: error: bare `super` is not supported; write `super()` to pass no arguments, or `super(...)` to pass them explicitly
      super
      ^^^^^
23.jasper:12:5: error: wrong number of arguments for `initialize` (given 0, expected 1)
      super
      ^^^^^

```

3. A method that returns nothing is not the same as one that returns `nil`

`return` with no argument is a `Void`, `return nil` is not the same. Jasper treats `nil` as a value. This allow me to identify at compile time things that can be chained (returning self)

4. Generics work by [monomorphization](https://en.wikipedia.org/wiki/Monomorphization) and methods can be _dropped_ from the generated code

`class Foo(T)` will create a version of all methods of `Foo` for that specific `T`, but sometimes, depending on the type of T, you might not want to deal with things differently.

Since there is no macros (and there will not be, at least not like Crystal) sometimes I need methods that can be used with certain types, so `where T : Reference` will work with Reference-type T, or `: Value`.

```crystal
class Cell(T)
  def initialize(@value : T)
  end

  def label : String where T : Reference
    "ref"
  end

  def label : String where T : Value
    "value"
  end

  def size : Int32 where T : Reference
    @value.size
  end
end

puts(Cell(String).new("hi").label) # ref
puts(Cell(Int32).new(3).label) # value
puts(Cell(String).new("hi").size) # 2

```

`Cell#size` is dropped from the list of methods when T is not a reference (Eg. Int32):

```crystal
puts(Cell(Int32).new(3).size)

```

```auto
$ bin/jasper run 24.jasper
24.jasper:21:6: error: undefined method `size` for Cell(Int32)
  puts(Cell(Int32).new(3).size) # value
       ^^^^^^^^^^^^^^^^^^^^^^^
  note: 24.jasper:13:26: dropped by this constraint
      def size : Int32 where T : Reference
                             ^^^^^^^^^^^^^

```

The idea is that `where` can be used with interfaces and you define methods for the different types without having to get into spaguetti macros (`{% if T < Reference %}`).

4. Catching for some early Crystal errors

The following segfaults in Crystal at runtime:

```auto
class Point
  @name : String

  def initialize(c : Bool)
    if c
      return
    end
    @name = "set" # skipped when c is true
  end

  def name : String
    @name
  end
end

puts(Point.new(true).name)

```

```auto
$ crystal run 25.jasper
Invalid memory access (signal 11) at address 0x4
[0x1020a76c4] *Exception::CallStack::print_backtrace:Nil +112 in /Users/luis/.cache/crystal/crystal-run-25.tmp

```

But is properly catch at compilation time by Jasper:

```auto
$ bin/jasper run 25.jasper
25.jasper:6:7: error: instance variable `@name` must be assigned on every path through `initialize`
        return
        ^^^^^^

```

This also catch another problem with calling methods in `initialize`:

```auto
class Base
  def initialize
    describe
  end

  def describe : Void
    puts("base")
  end
end

class Child < Base
  @name : String

  def initialize(name : String)
    super()
    @name = name
  end

  def describe : Void
    puts(@name.size)
  end
end

Child.new("jasper")

```

Above segfaults in Crystal but fails to compile in Jasper:

```auto
$ bin/jasper run 26.jasper
26.jasper:3:5: error: `describe` reaches `Child#describe`, which reads instance variable `@name` before it is assigned
      describe
      ^^^^^^^^

```

5. Explicitness everywhere: nothing is created or converted for you

Crystal will allow `class Shapes::Circle` when there is no `Shapes` defined and it will assume is a module. Jasper will not allow that:

```auto
$ bin/jasper run 27.jasper
namespace.jasper:1:1: error: `Shapes` is not defined, so `Shapes::Circle` has no parent

```

6. All options for Unions must be valid

If you used a Union in a type restriction, all the possible candidates of that union must be able to answer it, or you need to narrow it down:

```crystal
def show(v : Int32 | String) : Void
  puts(v.size)
end

show("abc")

```

```auto
$ bin/jasper run union.jasper
union.jasper:2:8: error: undefined method `size` for `Int32`, a member of `Int32 | String` (narrow first)
    puts(v.size)
         ^^^^^^

```

6. There are no exceptions or plans to have them

Exceptions are expensive and complicated when dealing with multiple threads and fibers (none currently implemented, btw)

Instead, I’m going on the path of a more lightweight, explicit treatment of errors: if a method returns an error, it explicitly says in the return type that it does and is up to you to handle it. Planning for some helpers in case you really don’t care about the error, or, like Crystal, methods where `nil` is an acceptable response for you to handle (like `Array(T)#[]?` or `Hash(K, V)#[]?`)

7. ARC means more memory management

Jasper frees memory when the last reference to it goes away, no GC. Right now there is no cycles intelligence, so the following program runs, but `a` and `b` are never freed:

```auto
Jasper frees memory when the last reference to it goes away, so there is no GC and no runtime to ship. The trade-off is cycles. This program runs, but a and b are never freed (you could link a cycle collector here, if one is planned).

class Node
  property other : Node? = nil
end

a = Node.new
b = Node.new
a.other = b
b.other = a # cycle, leaks today

```

---

_[View the full topic](https://forum.crystal-lang.org/t/jasper-a-crystal-like-programming-language-but-mostly-a-playground-for-theories/9206)._
