Instance
When defining many methods on a class, possibly with an interface wrapper, a short form is nice to reduce bloat.
To use the bindings from this module:
(import :std/instance)
Introduction
Gerbil has a strong type system which is close a "Meta-language"(ML
)! That is to say: a functional programming language with a polymorphic Hindley–Milner type system.
Let's take a Monad
typeclass as an example. Here's one in a Gofer
1 like syntax.
class Monad m where
return :: a -> m a
bind :: m a -> (a -> m b) -> m b
Using Gerbil's interface
's we can do something quite similar.
(import :std/interface)
;; The comments are just for show.
(interface Monad #; (m)
(return a) #;| -> m a |
(bind m a) #;| -> (a -> m b) -> m b |)
To declare an instance
of a typeclass also has some syntax. Here's an ML
instance declaration.
instance Monad Parser where
-- return :: a -> Parser a
return v = \inp -> [(v,inp)]
-- bind :: Parser a -> (a -> Parser b) -> Parser b
p ‘bind‘ f = \inp -> concat [f v out | (v,out) <- p inp]
A translation to gerbil uses Interface Passing Style. We'll take a struct as the interface object.
(defstruct parser ())
If using the builtin defmethod
it's not that far off the functional version.
(import :std/srfi/1)
;; return :: a -> Parser a
(defmethod {return parser} (lambda (_ v) (lambda (inp) [[v inp ...]])))
;; bind :: Parser a -> (a -> Parser b) -> Parser b
(defmethod {bind parser}
(lambda (_ p f)
(lambda (inp)
(append-map (cut match <> ([v . out] ((f v) out)))
(p inp)))))
But we can do better. Introducting the instance
syntax.
(instance Monad parser
;; return :: a -> Parser a
((return v) (lambda (inp) [[v inp ...]]))
;; bind :: Parser a -> (a -> Parser b) -> Parser b
((bind p f)
(lambda (inp) (append-map (cut match <> ([v . out] ((f v) out))) (p inp)))))
instance
Syntax
The (instance Interface Klass
method-def ...
[rebind: <boolean>])
<Interface>:
<symbol> ;; The name of the interface
(instance Interface) ;; both <symbol>'s, the name and instance of interface.
<boolean> ;; The interface is unused/blank
<Klass>:
<symbol> ;; The name of the class we are defining methods on
(self Klass) ;; both <symbol>'s, the name of the class and self thereof.
<method-def>:
((name . args) body ...) ;; The definition of a method. Note the
;; object is inferred so not in the lambda list.
<rebind>:
<boolean> ;; Default: #f ; Error if not true and any method already
;; been bound.. Otherwise rebind the klass.
The idea here is simply to reduce typing while putting things in a concise container.
interface
but no object
With an For example let's start with a bigger Monad
.
We've seen a Monad Interface.
By default Haskell has one more function that simply "combinds" two monadic functions.
class Monad m where
bind :: m a -> (a -> m b) -> m b
seq :: m a -> m b -> m b
return :: a -> m a
-- Minimal complete definition:
-- (>>=), return
m >> k = m >>= \_ -> k
We can add that to our interface:
(interface Monad
(return a) #;| -> m a |
(bind m a) #;| -> (a -> m b) -> m b |
(seq ma mb) #;| m a -> m b -> m b |)
And make the :t
class into an identity monad with a minimal complete definition.
(instance (m Monad) :t
((return a) a)
((bind ma f) (f ma))
((seq ma mb) (m.bind ma (lambda _ mb))))
The ((return a) a)
form expands to:
(defmethod {return :t} (lambda (self a) (using (m self : Monad) a)) rebind: #f)
A binding of self
is just made up and hygenic as it's not used.
And the seq
expansion becomes obvious as well.
(defmethod {seq :t}
(lambda (self ma mb) (using (m self : Monad) (m.bind ma (lambda _ mb))))
rebind: #f)
That now means that everyting is an instance of a monad, as the form said. We'll just use #t
as the object which is, after all, also of the :t
class, as is everything.
> (using (m #t : Monad)
(let* ((ma (m.return 41))
(mb (m.return 42)))
(m.seq ma mb)))
42
interface
and an object
Using a We can use interface passing style AND object-orientation together!
For example here's the start of a a parser similar to Parsec2.
(import :std/srfi/1)
(interface (Parser Monad) (item))
(defstruct parser (string))
(instance (m Parser) (self parser)
((item) (lambda (inp)
(def (%item i)
(cond ((number? i) ((m.return (string-ref self.string i)) (1+ i)))
((string? i) (if (zero? (string-length i)) []
(begin (set! self.string i) (%item 0))))))
(%item inp)))
((return v) (lambda (inp) [[v inp ...]]))
((bind p f)
(lambda (inp) (append-map (cut match <> ([v . out] ((f v) out))) (p inp)))))
Because it's a Monad
that means seq
is available.
> (def foop (parser ""))
> ((using (m foop : Parser)
(let* ((ma (m.return 41))
(mb (m.item)))
(m.seq ma mb))) "input")
((#\i . 1))
> (parser-string foop)
"input"
No interface, but an object and class.
In the item
method for the Parser/parser defined beforhand there's an %item
function that could be abstacted a few ways.
Keeping things dynamic there is no interface.
(import :std/ref)
(defstruct parsable (inp state))
(instance
#t (pa parsable)
((update-state fn) (set! pa.state (fn pa.state)) pa.state)
((ref (n 0)) (ref pa.inp n)))
So we can, dynamically, use and update and reference using the state. In this case we'll make the state
just the offset to peek and/or read.
> (def pstr (parsable "string" 0))
> {ref pstr}
#\s
> (using (pstr :- parsable) {pstr.ref pstr.state})
#\s
> (using (pstr :- parsable) {pstr.update-state 1+})
> (using (pstr :- parsable) {pstr.ref pstr.state})
#\t
rebind:
Time to Now that we've changed the way our parser does the state we should change the Parser/parser itself.
(defstruct parser ())
(instance (m Parser) (self parser)
((item) (lambda (inp)
(using (inp :- parsable)
(let (i {inp.ref inp.state})
{inp.update-state 1+}
((m.return i) inp)))))
((return v) (lambda (inp) [[v inp ...]]))
((bind p f)
(lambda (inp) (append-map (cut match <> ([v . out] ((f v) out))) (p inp))))
rebind: #t)
> (def inpp (parsable "foobar" 0))
> (caar ((using (p (parser) : Parser) (p.item)) inpp))
#\f
> (parsable-state inpp)
1
Footnotes
1 https://en.wikipedia.org/wiki/Gofer_(programming_language)