Skip to content

[Cookbook] Interfaces

Camilo edited this page Sep 9, 2021 · 4 revisions

Interfaces are not supported natively, until then a small workaround can be used to validate if a class implements some methods. Using Attributes we can create a small method to validate an interface.

class Implements {
  static validate(obj, interfaces) {
    if (!(obj is Class)) {
      obj = obj.type
    }
    if (!(interfaces is List)) {
      interfaces = [interfaces]
    }
    for (interface in interfaces) {
      for (method in interface.attributes.methods) {
        var i = method.key.indexOf("(")
        var key = method.key[0...i]
        if (method.value[null] && method.value[null].containsKey(key)) {
          var found = false
          for (item in obj.attributes.methods) {
            if (item.key == method.key) {
              found = true
              break
            }
          }
          if (found) {
            continue
          } else {
            Fiber.abort("%(obj.name) does not implement %(interface.name).%(method.key)")
          }
        }
      }
      return true
    }
  }

  static check(obj, interfaces) {
    var fn = Fiber.new{
      return validate(obj, interfaces)
    }
    fn.try()
    return fn.error == null
  }
}

class IPrint {
  #!print(message)
  print(message) {}
  
  #!print
  print(){}

  #!somethingElse
  #!json
  somethingElse(msg) {}
  
  #!ignore
  nonMatchingName() {}
}

class Printer {
  #!somethingElse
  somethingElse(msg) {}

  #!print(message)
  print(message){
    System.print(message)
  }
  
  #!print
  print(){
    System.print("Hello")
  }
}

class Writer {
  #!print(message)
  print(message){
    System.print(message)
  }
}

System.print(Implements.check(Printer, IPrint))
System.print(Implements.validate(Printer, IPrint))
System.print(Implements.check(Writer, IPrint))
System.print(Implements.validate(Writer, IPrint))

attributes.methods

Here is an example using the attributes.methods property.

#!protocol
class JsonEncodingProtocol {
  
  #!required
  toJson{}
  
  #!required
  toJSON{}
  
  static methods {
    var methods = this.attributes.methods
    var required = []
    var optional = []
    
    for (method in methods) {
      for (attributes in method.value) {
         for (attribute in attributes.value) {
           if (attribute.key == "required") {
             required.add(method.key)
           }
           
           if (attribute.key == "optional") {
             optional.add(method.key)
           }
         }
      }
    }
    
    return {
      "required": required,
      "optional": optional
    }
  }
  
  static implements(Entity) {
    
    if (!Entity.attributes) {
     return false
    }
    
    // quick hack to get the protocol name
    // must be changed if multiple protocols are present
    var protocol = Entity.attributes.self.keys.toList[0]
    
    var candidates = []
    
    for(method in Entity.attributes.self.values) {
     for (attribute in method.keys) {
         // We have to check if the entity
         // implements this protocol
         if (protocol == this.name) {
           candidates.add(attribute)
         }
     }
    }
    
    for (method in this.methods["required"]) {
      if (!candidates.contains(method)) {
        // Fiber.abort("required method %(method) not found")
        return false
      }
    }
    
    for (method in this.methods["optional"]) {
      if (!candidates.contains(method)) {
        // System.print("optional method %(method) not implemented")
      }
    }
    
    return true
  }
}

// We specify which methods of the protocol
// we are implementing

#!JsonEncodingProtocol(
  toJson,
  toJSON)
class MyObject {
  construct new(){}
  
  // does nothing, just for tell its the protocol implementation
  #region = JsonEncodingProtocol
  
  toJson {"Hello"}
  toJSON {toJson}
}

var myobj = MyObject.new()

// Must be true
System.print(JsonEncodingProtocol.implements(myobj.type))