carp-lang / Carp

A statically typed lisp, without a GC, for real-time applications.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

should `the` should perform coercion?

scolsen opened this issue · comments

Our the form currently doesn't coerce values on its own--it only enforces that some value t is of a given type:

(Int.+ (the Int \a) 1)
I can’t match the types `Int` and `Char`.
(the Int \c) : Int
 At line 3, column 8 in 'REPL'
At line 3, column 17 in 'REPL' at REPL:3:1.

But this sort of check will be performed by the type checker anyway, without the the form.

To actually coerce a value, one needs to combine Unsafe.coerce and the:

(Int.+ (the Int (Unsafe.coerce \a)) 1)
98

Now, there are pros and cons to this approach:

  • pros
    • it's very clear what's going on, and the additional verbosity helps ensure programmers avoid mistakes
    • coerce is unsafe, which communicates that the operation may cause a runtime error
  • cons
    • this is much more verbose than only writing a single the or single coerce
    • the by itself seemingly does nothing

So, I'd pose the question, should we make it so that the performs the coercion? Or should we drop the altogether. in favor of a more complex coerce implementation that takes a direct type argument (Unsafe.coerce Int \a)?

I feel like the is like a type annotation in e.g. Haskell—a necessary evil to cope with the shortcomings of type checking. I know that sometimes you need it, for instance when defining prn based on str for a type:

(deftype X [])

(defmodule X
  (defn prn [x] (str x)) ; x is generic here!
  (defn prn [x] (str (the X x))) ; this makes sure the type checker knows
  (defn prn [x] (X.str x)) ; the alternative
)

I don’t think overloading it to also do conversion is sensible, since I think it actually does the opposite: it ensures something is not of any other type, whereas coerce ensures that any other type will be made to conform.

Does that make sense?

I feel like the is like a type annotation in e.g. Haskell—a necessary evil to cope with the shortcomings of type checking. I know that sometimes you need it, for instance when defining prn based on str for a type:

(deftype X [])

(defmodule X
  (defn prn [x] (str x)) ; x is generic here!
  (defn prn [x] (str (the X x))) ; this makes sure the type checker knows
  (defn prn [x] (X.str x)) ; the alternative
)

I don’t think overloading it to also do conversion is sensible, since I think it actually does the opposite: it ensures something is not of any other type, whereas coerce ensures that any other type will be made to conform.

Does that make sense?

Yes, it does! Thanks for clearing that up.

So it's essentially only forcing concretizations on its lonesome, but in combination with coerce, can do more, makes sense!

You can achieve the same thing with functions using, sig too. That's how I usually do it:

(deftype X [])

(defmodule X
  (sig prn (Fn [X] String)) ; this makes sure the type checker knows
  (defn prn [x] (str x))
)

I guess a follow up question would be, do we need both sig and the? The flexibility is pretty nice since (the X x) is shorter than adding a signature and you may only want to force on particular part of a function, keeping the rest generic (though this is achievable with sig as well).

edit: fixed a couple errors -- typing too quickly 🙃

I’ve found myself having to use both sig and the in the past. That’s probably a bug, but until that’s fixed, we need both! :)

I have found myself having to locally annotate expressions in big functions as well, though I can’t find any examples right now.

A good example for a small function that lives and dies with the is Vector.last from dimensions:

  (doc last
    "Returns the last element of a Vector.")
  (sig last (Fn [(Ref (Vector (Size n) a))] a))
  (defn last [v]
    @(Array.unsafe-nth (Vector.elements v) (to-int (the (Phantom n) (Phantom.init)))))

It has a sig, but needs a local the anyway.

Good examples! Definitely seems like we need it right now. I do think some of the sig+the cases are probably bugs. At any rate it's probably good to have the flexibility of both forms regardless, so I think we can close this.

Thanks for the discussion!