I implemented two optimizations for the `Option` type. The idea was to do the 
same as the Rust compiler, use the nil/nullptr for the none option without 
occupying extra memory. Then I had a similar idea for `Option[Natural]`, where 
I could use `-1` internally to store none. That type could be used for the 
`find` procedure so that it does not return `int` anymore, but 
`Option[Natural]` without overhead.

Here is the code, maybe if there is interest this might become a pull request. 
The think I don't like about this implementation is, `Option[Natural]` 
initializes to Some(0), and not None.

Here is the code:
    
    
    import typetraits, macros
    
    type
      PointerType = ptr|ref|string|seq
      Option*[T] = object
        ## An optional type that stores its value and state separately in a 
boolean.
        when T is Natural:
          val: int   # use -1 for none
        elif T is PointerType:
          val: T     # use nil for none
        else:
          val: T
          has: bool  # fallback to use an extra field
      
      UnpackError* = ref object of ValueError
    
    proc some*[T](val: T): Option[T] =
      ## Returns a ``Option`` that has this value.
      when T is Natural:
        result.val = int(val)
      elif T is seq or T is string or T is ptr or T is ref:
        assert val != nil
        result.val = val
      else:
        #assert(false == T is seq or T is string or T is ptr or T is ref)
        result.has = true
        result.val = val
    
    proc none*(T: typedesc): Option[T] {.inline.} =
      ## Returns a ``Option`` for this type that has no value.
      when T is Natural:
        result.val = -1
      elif T is seq or T is string or T is ptr or T is ref:
        result.val = nil # does nothing just for clarity
      else:
        result.has = false
    
    proc option*[T,U](t: typedesc[T], val: U): Option[T] {.inline.} =
      when T is Natural:
        result.val = max(-1, val)
      elif T is seq or T is string or T is ptr or T is ref:
        result.val = val # does nothing just for clarity
      else:
        result.val = T(val)
        result.has = true
    
    proc isSome*[T](self: Option[T]): bool {.inline.} =
      when T is Natural:
        self.val >= 0
      elif T is seq or T is string or T is ptr or T is ref:
        not self.val.isNil
      else:
        self.has
    
    proc isNone*[T](self: Option[T]): bool {.inline.} =
      when T is Natural:
        self.val < 0
      elif T is seq or T is string or T is ptr or T is ref:
        self.val.isNil
      else:
        not self.has
    
    proc unsafeGet*[T](self: Option[T]): T =
      ## Returns the value of a ``some``. Behavior is undefined for ``none``.
      assert self.isSome
      self.val
    
    proc get*[T](self: Option[T]): T =
      ## Returns contents of the Option. If it is none, then an exception is
      ## thrown.
      if self.isNone:
        raise UnpackError(msg : "Can't obtain a value from a `none`")
      self.val
    
    proc get*[T](self: Option[T], otherwise: T): T =
      ## Returns the contents of this option or `otherwise` if the option is 
none.
      if self.isSome:
        self.val
      else:
        otherwise
    
    proc map*[T](self: Option[T], callback: proc (input: T)) =
      ## Applies a callback to the value in this Option
      if self.isSome:
        callback(self.val)
    
    proc map*[T, R](self: Option[T], callback: proc (input: T): R): Option[R] =
      ## Applies a callback to the value in this Option and returns an option
      ## containing the new value. If this option is None, None will be returned
      if self.isSome:
        some[R]( callback(self.val) )
      else:
        none(R)
    
    proc filter*[T](self: Option[T], callback: proc (input: T): bool): 
Option[T] =
      ## Applies a callback to the value in this Option. If the callback returns
      ## `true`, the option is returned as a Some. If it returns false, it is
      ## returned as a None.
      if self.isSome and not callback(self.val):
        none(T)
      else:
        self
    
    proc `==`*(a, b: Option): bool {.inline.} =
      ## Returns ``true`` if both ``Option``s are ``none``,
      ## or if they have equal values
      (a.isSome and b.isSome and a.val == b.val) or (a.isNone and b.isNone)
    
    proc `$`*[T]( self: Option[T] ): string =
      ## Returns the contents of this option or `otherwise` if the option is 
none.
      if self.isSome:
        "Some(" & $self.val & ")"
      else:
        "None[" & T.name & "]"
    
    when isMainModule:
      import unittest, sequtils, macros
      
      suite "options":
        # work around a bug in unittest
        let intNone = none(Natural)
        let stringNone = none(string)
        
        test "has has":
          var
            a: Option[int]
            b: Option[Natural]
            c: Option[string]
            d: Option[ptr int]
            e: Option[ref int]
            f: Option[seq[int]]
          
          check( compiles(a.has) )
          check( not compiles(b.has) )
          check( not compiles(c.has) )
          check( not compiles(d.has) )
          check( not compiles(e.has) )
          check( not compiles(f.has) )
        
        test "example":
          proc find(haystack: string, needle: char): Option[Natural] =
            for i, c in haystack:
              if c == needle:
                return some Natural(i)
            none(Natural)
          
          check("abc".find('c').get() == 2)
    
    [...]
    

Reply via email to