Sigils release v0.21.0 includes selectors -- dynamic runtime methods and protocols!
Sigils has proven very useful to me for a couple of work projects. It provides a way to do dynamic event oriented programming in Nim (e.g. PubSub), similar to QT for C++., using signals and slots. They allow decoupling problems domains or modifying behavior dynamically.
However signals and slots don't return values or handle responder chains, which is what Cocoa builds on from Obj-C. This especially limits some aspects of GUI programming.
So I decided to try building them using Sigils' signals and slots! I ended up calling them selectors and they use a new DynamicAgent type. The idea is to provide runtime methods. They also provide protocols which any DynamicAgent can implement and which can be queried at runtime. You can read more about them in their manual.
What's very useful about this approach over say Nim's native methods is the ability to one-of modify a given GUI element with a new behavior. In a fashion they're just named callbacks, but with more idioms and syntax for dealing with high level needs of GUIs rather than mucking about with callback isNil fields.
Here's a simple example:
import sigils/selectors
type
Post = ref object of DynamicAgent
title: string
draft: bool
protocol PostDisplay:
method displayTitle(): string
method canPublish(): bool
method normalTitle(self: Post): string {.selector.} = self.title
method draftTitle(self: Post): string {.selector.} = "[draft] " & self.title
method postCanPublish(self: Post): bool {.selector.} = not self.draft
let post = Post(title: "Selectors in Sigils", draft: true)
discard post.replaceMethods(PostDisplay, [
displayTitle => normalTitle,
canPublish => postCanPublish,
])
doAssert post.hasAdopted(PostDisplay)
echo post.displayTitle() #=> Selectors in Sigils
discard post.replaceMethod(displayTitle, draftTitle) # Override display behavior for this one post
echo post.displayTitle() #=> [draft] Selectors in Sigils
echo post.canPublish() #=> false
Here PostDisplay is the protocol: it says a conforming object can answer displayTitle and canPublish. The post adopts it when replaceMethods(PostDisplay, ...) installs implementations for the required selectors. Later, replaceMethod overrides only displayTitle for that specific instance.
There's convenience syntax for defining and implementing a protocol in one go:
protocol PostDisplay from Post:
method displayTitle(self: Post): string =
if self.draft:
"[draft] " & self.title
else:
self.title
method canPublish(self: Post): bool =
not self.draft
let post = Post.newProto(title = "Selectors in Sigils", draft = true)
Here's a short example using pushMethod and popMethod helpers. They're useful in GUI programming contexts where you might want to customize some action, but only temporarily in a certain context:
proc addAdminPrefix(self: DynamicAgent, inv: var Invocation, next: DynamicMethod) =
next(self, inv)
if inv.handled: inv.setResult("[admin] " & inv.resultAs(string))
let post = Post.newProto(title = "Selectors in Sigils", draft = true)
echo post.displayTitle()#=> [draft] Selectors in Sigils
block customizeMethod:
let token = post.pushMethod(displayTitle, addAdminPrefix)
echo post.displayTitle()#=> [admin] [draft] Selectors in Sigils
discard token.popMethod()
echo post.displayTitle()#=> [draft] Selectors in Sigils