Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

re-design classes to support automatic code generation #86

Open
dedbox opened this issue Aug 14, 2019 · 1 comment
Open

re-design classes to support automatic code generation #86

dedbox opened this issue Aug 14, 2019 · 1 comment
Labels
enhancement New feature or request

Comments

@dedbox
Copy link
Owner

dedbox commented Aug 14, 2019

The "old" (or current as of this writing) implementation of classes and class instances eschews types entirely, so I wind up manually resolving each use of a class member to a particular instance of the class. This makes for an awkward programming experience.

In practice, old classes are approximately first-order abstractions -- a simple class like Functor is useful in limited contexts, but higher order classes like Arrow or V (abstract vector spaces) are impractical. To fully express the constructs in Haskell's diagrams library as classes, Algebraic Racket needs a "new" class syntax that actively supports higher order abstractions.

An illustrative example in the old syntax:

;;; functor.rkt

(class Functor
  [fmap]
  minimal ([fmap]))

;;; box-functor.rkt

(define-syntax box-Functor
  (instance Functor
      [fmap (λ (f b) (box (f (unbox b))))]))

;;; list-functor.rkt

(define-syntax list-Functor
  (instance Functor
      [fmap map]))

;;; maybe-functor.rkt

(define-syntax Maybe-Functor
  (instance Functor
      [fmap (function*
              [(_ Nothing) Nothing]
              [(f (Just a)) (Just (f a))])]))

;;; functor-dispatch.rkt

(define fmap
  (let ([  box-fmap (with-instance   box-Functor fmap)]
        [ list-fmap (with-instance  list-Functor fmap)]
        [Maybe-fmap (with-instance Maybe-Functor fmap)])
    (λ (f a)
      ((cond [( box? a)  box-fmap]
             [(list? a) list-fmap]
             [(Maybe? a) Maybe-fmap]
             [else (error "fmap: no instance for Functor")])
       f a))))

Dynamic dispatch boilerplate is tedious to write by hand, but it can be generated automatically whenever what to generate and where can be deduced from the code. To this end, the "new" class form will move to Racket-style internal definitions and a generic form (:) for member "type" annotations.

Here's the same example in the new syntax:

;;; functor.rkt

(require algebraic/types/dynamic)

(class Functor
  #:minimal fmap
  (: fmap (-> procedure? Functor? ... Functor?)))

;;; box-functor.rkt

(instance Functor Box
  (: fmap (-> procedure? box? ... box?))
  (define (fmap f . bs) (box ($ f (map unbox bs)))))

;;; list-functor.rkt

(instance Functor List
  (define fmap map))

;;; maybe-functor.rkt

(require algebraic/types/simple)

(instance Functor Maybe
  (: fmap (-> (-> a b) (Maybe a) (Maybe b)))
  (define fmap
    (function*
      [(_ Nothing) Nothing]
      [(f (Just a)) (Just (f a))])))

Note the absence of functor-dispatch.rkt in this version, which uses a hypothetical algebraic/types/dynamic module to associate contracts bound by : to member definitions. It would generate code that looks something like this:

;;; functor.rkt

(define-syntax Functor (class-transformer #'Functor ...))

(define Functor? (|| box? list? Maybe?))

(define/contract (fmap f . args) (-> procedure? Functor? ... Functor?)
  ($ (cond [(box? f) box-fmap]
           [(list? f) list-fmap]
           [(Maybe? f) Maybe-fmap])
     f args))

;;; box-functor.rkt

(define-syntax Box-Functor (class-instance-transformer #'Functor #'Box ...))

(define/contract (box-fmap f . bs) (-> procedure? box? ... box?)
  (box ($ f (map unbox bs))))

;;; list-functor.rkt

(define-syntax List-Functor (class-instance-transformer #'Functor #'List ...))

(define/contract list-fmap (-> procedure? Functor? ... Functor?) map)

;;; maybe-functor.rkt

(define-syntax Maybe-Functor (class-instance-transformer #'Functor #'Maybe ...))

(define Maybe-fmap
  (function*
    [(_ Nothing) Nothing]
    [(f (Just a)) (Just (f a))]))
@dedbox
Copy link
Owner Author

dedbox commented Aug 14, 2019

How to handle unconstrained domain?

Ex:

(define return #:-> (unconstrained-domain-> Monad?) pure)

I see two obvious choices:

  1. add keyword argument #:unconstrained-domain->
  2. treat unconstrained-domain-> at the top level as a special case

Either way, each of the many kinds of ->-like contracts would require work.

A more generic keyword argument (e.g. #::) avoids the issue and broadens its applicability to non-procedure values.

Ex:

(define return #:: (unconstrained-domain-> Monad?) pure)

@dedbox dedbox changed the title add #:-> contract keyword arg to defining forms add #:: contract keyword arg to defining forms Aug 14, 2019
@dedbox dedbox changed the title add #:: contract keyword arg to defining forms re-design classes to support automatic code generation Aug 17, 2019
@dedbox dedbox added the enhancement New feature or request label Aug 23, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant