racket-algebraic icon indicating copy to clipboard operation
racket-algebraic copied to clipboard

add newtype declarations

Open dedbox opened this issue 4 years ago • 1 comments

A special notation for representing ordinary Racket data as a single-field product type.

newtype instances are indistinguishable from their field at run time. Their main purpose is to provide a basis for code generation and future development into modular newtype-aware type system components.

In the following example,

(1newtype List list? list (>> $ id))

a List is any value recognized by the List? predicate, which in this case is bound to list?. At run time, instances of the type can be created with the List constructor, here bound to list. Instances can also be matched against the List pattern and then de-constructed further.

And finally, a List can be unListed, which in this case returns the elements of the list as multiple distinct values. If unList does not receive exactly one argument, or if its sole argument is not a List?, a run-time error is raised.

The Short Form

Takes 3 or 4 non-keyword arguments.

Arguments:

  • type name <T>
  • predicate value
  • constructor value
  • optional de-constructor value

Defines:

  • type descriptor type:<T>
  • match pattern <T>
  • predicate function <T>?
  • constructor function <T>
  • optional de-constructor function un<T>

The Long Form

Takes 3 to 5 keyword arguments.

Required Keyword Arguments:

  • type name <T>
  • predicate name <T?> and value
  • constructor name <TC> and value

Optional Keyword Argument:

  • optional match pattern name <TM>
  • optional de-constructor name <unT> and value

Defines:

  • type description type:<T>
  • match pattern <TM> or <T>
  • predicate function <T?>
  • constructor function <TC>
  • optional de-constructor function <unT>

More Examples

;;; short form
(newtype List list? list)

;;; defaults:
;;;   match pattern name: List
;;;       predicate name: List?
;;;     constructor name: List
;;; 
;;; no de-constructor
 
(newtype Pair pair? :: (φ (a . b) (id a b)))

;;; defaults:
;;;    match pattern name: Pair
;;;        predicate name: Pair?
;;;      constructor name: Pair
;;;   de-constructor name: unPair

;;; Arguments in a box!
(newtype Args                           ;type name
         (&& box? (.. list? unbox))     ;predicate value
         (.. box list)                  ;constructor value
         (.. id unbox))                 ;de-constructor value

;;; defaults:
;;;    match pattern name: Args
;;;        predicate name: Args?
;;;      constructor name: Args
;;;   de-constructor name: unArgs

;;; Long form
(newtype State
         #:match state
         #:predicate [state? number?]
         #:constructor [state id]
         #:de-constructor [get-state id])

dedbox avatar Aug 12 '19 01:08 dedbox

newtype has three main use cases:

  1. hide implementation details through indirection
  2. create many instances of a class with the same underlying type
  3. create class instances for Racket data types

Indirection

(class WidgetImpl s
  (: make-widget (-> s (WidgetImpl s)))
  (: widget-state (-> (WidgetImpl s) s))
  (: widget? (-> a Bool)))
(newtype Widget                      ;The Widget (sum) type
         [Widget make-widget]        ;is implemented by make-widget,
         [Widget? widget?]           ;can be recognized by its implementation,
         [Widget-state (φ (Foo s) s)])  ;and carries a "state" value

Now I can export Widget as an API without leaking the details of how it's implemented:

(provide (newtype-out Widget))

;;; or, equivalently:

(provide (sum Widget) Widget Widget? Widget-state)

Many Instances

This appears to be its main use in Haskell. The class system is evolving to accommodate automatic code generation, initially for run-time type checks and dynamic dispatch. The changes will effectively turn classes into an opt-in mechanism for type-directed code generation and lay the groundwork for compile-time optimizations by class-aware type checker modules.

Before any code can be generated, the types must be analyzed. This means the types must be specified in the code or inferred automatically (see #86). With respect to code generation, each newtype alias has its own set of instances distinct from all the others.

Racket data

newtype allows any function to be used as a constructor or de-constructor, enabling class instances for built-in data types, such as boxes:

(newtype Box
         [Box box]
         [Box? box?]
         [unBox unbox])
(instance WidgetImpl Box
  (define make-widget Box)
  (define widget? Box?)
  (define widget-state unBox))

or lists:

(newtype List
         [List list]
         [List? list?]
         [unList id])
(instance WidgetImpl List
  (define make-widget List)
  (define widget? List?)
  (define widget-state unList))

dedbox avatar Aug 15 '19 19:08 dedbox