Skip to content

Commit

Permalink
Introduce types for forward compiler compatiblity
Browse files Browse the repository at this point in the history
After Crystal 0.15, compiler will require declare the types used
by instance variables on classes.

This require changes to the usage of `Radix::Tree` by introducing
the type of payload elements it will handle:

    # Will only support symbols as payload
    tree = Radix::Tree(Symbol).new
    tree.add "/", :root

    # Error: cannot add node with anything other than Symbol
    tree.add "/meaning-of-life", 42

The changes ensure future compatibility with Crystal and also
enforces a more declarative usage of `Radix::Tree`.

If necessary, you can combine multiple types to ensure a tree
can contain all the wide range of payloads you need:

    tree = Radix::Tree.new(Foo | Bar | Symbol).new
    tree.add "/", :root
    tree.add "/foo", foo_instance

This change includes:

- Tree, Node and Result has been updated to require types.
- Node is capable of have optional payload (from defined type).
- Documentation has been updated to reflect this change.
  • Loading branch information
luislavena committed Apr 16, 2016
1 parent 18bf9b1 commit 9003075
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 109 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,15 @@ All notable changes to Radix project will be documented in this file.
This project aims to comply with [Semantic Versioning](http://semver.org/), This project aims to comply with [Semantic Versioning](http://semver.org/),
so please check *Changed* and *Removed* notes before upgrading. so please check *Changed* and *Removed* notes before upgrading.


## [Unreleased]
### Fixed
- Improve forward compatibility with newer versions of the compiler by adding
missing types to solve type inference errors.

### Changed
- `Radix::Tree` now requires the usage of a type which will be used as node's
payload. See [README](README.md) for details.

## [0.2.1] - 2016-03-15 ## [0.2.1] - 2016-03-15
### Fixed ### Fixed
- Correct `Result#key` incorrect inferred type. - Correct `Result#key` incorrect inferred type.
Expand Down
38 changes: 32 additions & 6 deletions README.md
@@ -1,11 +1,11 @@
# Radix Tree # Radix Tree


[![Build Status](https://travis-ci.org/luislavena/radix.svg?branch=master)](https://travis-ci.org/luislavena/radix)
[![docrystal.org](http://docrystal.org/badge.svg?style=round)](http://docrystal.org/github.com/luislavena/radix)

[Radix tree](https://en.wikipedia.org/wiki/Radix_tree) implementation for [Radix tree](https://en.wikipedia.org/wiki/Radix_tree) implementation for
Crystal language Crystal language


[![Build Status](https://travis-ci.org/luislavena/radix.svg?branch=master)](https://travis-ci.org/luislavena/radix)
[![docrystal.org](http://docrystal.org/badge.svg?style=round)](http://docrystal.org/github.com/luislavena/radix)

## Installation ## Installation


Add this to your application's `shard.yml`: Add this to your application's `shard.yml`:
Expand All @@ -18,12 +18,14 @@ dependencies:


## Usage ## Usage


### Building Trees

You can associate a *payload* with each path added to the tree: You can associate a *payload* with each path added to the tree:


```crystal ```crystal
require "radix" require "radix"
tree = Radix::Tree.new tree = Radix::Tree(Symbol).new
tree.add "/products", :products tree.add "/products", :products
tree.add "/products/featured", :featured tree.add "/products/featured", :featured
Expand All @@ -34,6 +36,30 @@ if result.found?
end end
``` ```


The types allowed for payload are defined on Tree definition:

```crystal
tree = Radix::Tree(Symbol).new
# Good, since Symbol is allowed as payload
tree.add "/", :root
# Compilation error, Int32 is not allowed
tree.add "/meaning-of-life", 42
```

Can combine multiple types if needed:

```crystal
tree = Radix::Tree(Int32 | String | Symbol).new
tree.add "/", :root
tree.add "/meaning-of-life", 42
tree.add "/hello", "world"
```

### Lookup and placeholders

You can also extract values from placeholders (as named segments or globbing): You can also extract values from placeholders (as named segments or globbing):


```crystal ```crystal
Expand All @@ -53,8 +79,8 @@ Please see `Radix::Tree#add` documentation for more usage examples.
Pretty much all Radix implementations have their limitations and this project Pretty much all Radix implementations have their limitations and this project
is no exception. is no exception.


When designing and adding *paths* to build a Tree, please consider that two When designing and adding *paths* to a Tree, please consider that two different
different named parameters cannot share the same level: named parameters cannot share the same level:


```crystal ```crystal
tree.add "/", :root tree.add "/", :root
Expand Down
40 changes: 23 additions & 17 deletions spec/radix/node_spec.cr
Expand Up @@ -4,7 +4,7 @@ module Radix
describe Node do describe Node do
describe "#key=" do describe "#key=" do
it "accepts change of key after initialization" do it "accepts change of key after initialization" do
node = Node.new("abc") node = Node(Nil).new("abc")
node.key.should eq("abc") node.key.should eq("abc")


node.key = "xyz" node.key = "xyz"
Expand All @@ -23,39 +23,45 @@ module Radix
node.payload.should eq(1_000) node.payload.should eq(1_000)
end end


# This example focuses on the internal representation of `payload`
# as inferred from supplied types and default values.
#
# We cannot compare `typeof` against `property!` since it excludes `Nil`
# from the possible types.
it "makes optional to provide a payload" do it "makes optional to provide a payload" do
node = Node.new("abc") node = Node(Int32).new("abc")
node.payload?.should be_falsey node.payload?.should be_falsey
typeof(node.@payload).should eq(Int32 | Nil)
end end
end end


describe "#priority" do describe "#priority" do
it "calculates it based on key size" do it "calculates it based on key size" do
node = Node.new("a") node = Node(Nil).new("a")
node.priority.should eq(1) node.priority.should eq(1)


node = Node.new("abc") node = Node(Nil).new("abc")
node.priority.should eq(3) node.priority.should eq(3)
end end


it "returns zero for catch all (globbed) key" do it "returns zero for catch all (globbed) key" do
node = Node.new("*filepath") node = Node(Nil).new("*filepath")
node.priority.should eq(0) node.priority.should eq(0)


node = Node.new("/src/*filepath") node = Node(Nil).new("/src/*filepath")
node.priority.should eq(0) node.priority.should eq(0)
end end


it "returns one for keys with named parameters" do it "returns one for keys with named parameters" do
node = Node.new(":query") node = Node(Nil).new(":query")
node.priority.should eq(1) node.priority.should eq(1)


node = Node.new("/search/:query") node = Node(Nil).new("/search/:query")
node.priority.should eq(1) node.priority.should eq(1)
end end


it "changes when key changes" do it "changes when key changes" do
node = Node.new("a") node = Node(Nil).new("a")
node.priority.should eq(1) node.priority.should eq(1)


node.key = "abc" node.key = "abc"
Expand All @@ -71,10 +77,10 @@ module Radix


describe "#sort!" do describe "#sort!" do
it "orders children by priority" do it "orders children by priority" do
root = Node.new("/") root = Node(Int32).new("/")
node1 = Node.new("a") node1 = Node(Int32).new("a", 1)
node2 = Node.new("bc") node2 = Node(Int32).new("bc", 2)
node3 = Node.new("def") node3 = Node(Int32).new("def", 3)


root.children.push(node1, node2, node3) root.children.push(node1, node2, node3)
root.sort! root.sort!
Expand All @@ -85,10 +91,10 @@ module Radix
end end


it "orders catch all and named parameters lower than others" do it "orders catch all and named parameters lower than others" do
root = Node.new("/") root = Node(Int32).new("/")
node1 = Node.new("*filepath") node1 = Node(Int32).new("*filepath", 1)
node2 = Node.new("abc") node2 = Node(Int32).new("abc", 2)
node3 = Node.new(":query") node3 = Node(Int32).new(":query", 3)


root.children.push(node1, node2, node3) root.children.push(node1, node2, node3)
root.sort! root.sort!
Expand Down
26 changes: 13 additions & 13 deletions spec/radix/result_spec.cr
Expand Up @@ -5,15 +5,15 @@ module Radix
describe "#found?" do describe "#found?" do
context "a new instance" do context "a new instance" do
it "returns false when no payload is associated" do it "returns false when no payload is associated" do
result = Result.new result = Result(Nil).new
result.found?.should be_false result.found?.should be_false
end end
end end


context "with a payload" do context "with a payload" do
it "returns true" do it "returns true" do
node = Node.new("/", :root) node = Node(Symbol).new("/", :root)
result = Result.new result = Result(Symbol).new
result.use node result.use node


result.found?.should be_true result.found?.should be_true
Expand All @@ -24,15 +24,15 @@ module Radix
describe "#key" do describe "#key" do
context "a new instance" do context "a new instance" do
it "returns an empty key" do it "returns an empty key" do
result = Result.new result = Result(Nil).new
result.key.should eq("") result.key.should eq("")
end end
end end


context "given one used node" do context "given one used node" do
it "returns the node key" do it "returns the node key" do
node = Node.new("/", :root) node = Node(Symbol).new("/", :root)
result = Result.new result = Result(Symbol).new
result.use node result.use node


result.key.should eq("/") result.key.should eq("/")
Expand All @@ -41,9 +41,9 @@ module Radix


context "using multiple nodes" do context "using multiple nodes" do
it "combines the node keys" do it "combines the node keys" do
node1 = Node.new("/", :root) node1 = Node(Symbol).new("/", :root)
node2 = Node.new("about", :about) node2 = Node(Symbol).new("about", :about)
result = Result.new result = Result(Symbol).new
result.use node1 result.use node1
result.use node2 result.use node2


Expand All @@ -54,8 +54,8 @@ module Radix


describe "#use" do describe "#use" do
it "uses the node payload" do it "uses the node payload" do
node = Node.new("/", :root) node = Node(Symbol).new("/", :root)
result = Result.new result = Result(Symbol).new
result.payload?.should be_falsey result.payload?.should be_falsey


result.use node result.use node
Expand All @@ -64,8 +64,8 @@ module Radix
end end


it "allow not to assign payload" do it "allow not to assign payload" do
node = Node.new("/", :root) node = Node(Symbol).new("/", :root)
result = Result.new result = Result(Symbol).new
result.payload?.should be_falsey result.payload?.should be_falsey


result.use node, payload: false result.use node, payload: false
Expand Down

0 comments on commit 9003075

Please sign in to comment.