Slide 1

Slide 1 text

Refining refinements Shugo Maeda 2013-05-30T17:30:00 Network Applied Communication Laboratory Ltd.

Slide 2

Slide 2 text

What are refinements? Local class extensions • Adding methods to, or changing methods in existing classes • Activated in a local scope 1

Slide 3

Slide 3 text

Example 2 module Rationalize refine Fixnum do def /(other) quo(other) end end end p 1 / 2 #=> 0 using Rationalize p 1 / 2 #=> (1/2)

Slide 4

Slide 4 text

Basic concepts Packaged in a module • Multiple refinements can be included in a single module Module#refine(klass) • Defines a new refinement for klass in the receiver main.using(mod) • Activates refinements in mod • main is self at toplevel (i.e., only available at toplevel) • Refinements are activated only in the file using is called • i.e., if control is transferred to another file (e.g., by a method call), refinements are deactivated 3

Slide 5

Slide 5 text

What are refinements for? For better compatibility • Trying breaking changes • Backward compatibility For internal DSL • Extensions for built-in classes 4

Slide 6

Slide 6 text

Trying breaking changes Breaking changes • Changes which break other code • Incompatible changes in existing methods • Method removal • Method addition (changes the behavior of method_missing) Implement such changes as refinements • They are activated locally • Files not using them never be broken Then, make them global if necessary Example • Integer#/ 5

Slide 7

Slide 7 text

Backward compatibility Save existing code from braking changes Example 6 module OldChars refine String do def chars; each_char; end end end p "foo".chars #=> ["f", "o", "o"] using OldChars p "foo".chars #=> #

Slide 8

Slide 8 text

Internal DSL Extensions for built-in classes • Literals • Fixnum, Bignum, Float, String, Symbol, Array, Hash... Limit extensions to DSL using refinements 7

Slide 9

Slide 9 text

Case study radd_djur • https://github.com/shugo/radd_djur • Packrat parser combinator library 8

Slide 10

Slide 10 text

Packrat parser Top-down parser • with backtracking • and memoization 9

Slide 11

Slide 11 text

Parser combinator Allows you to combine small parsers into one big parser Each parser is a monad What's a monad? • Ask Haskell guys 10

Slide 12

Slide 12 text

PEG Parsing Expression Grammar Formal grammar Ambiguity free 11

Slide 13

Slide 13 text

Example of PEG 12 additive <- multitive '+' additive / multitive multitive <- primary '*' multitive / primary primary <- '(' additive ')' / digits digits <- [0-9]+

Slide 14

Slide 14 text

Example of a RaddDjur parser 13 using RaddDjur::DSL g = RaddDjur::Grammar.new(:additive) { # additive <- multitive '+' additive / multitive define :additive do :multitive.bind { |x| "+".bind { :additive.bind { |y| ret x + y } } } / :multitive end }

Slide 15

Slide 15 text

define Define a new nonterminal and its rule 14 define(name, parser) define(name) { parser }

Slide 16

Slide 16 text

bind >>= (bind) in Haskell sequence in PEG (e.g., a b) Example: • First, invoke the parser a • If succeeded, invoke the parser b on the remainder of the input string left unconsumed by a • If succeeded, return the result 15 a.bind { |x| b.bind { |y| ret [x, y] }

Slide 17

Slide 17 text

ret return in Haskell Used to return the parsing result Example: • Return the value of x.to_i as the parsing result of (?0..?9). 16 (?0..?9).bind { |x| ret x.to_i }

Slide 18

Slide 18 text

/ / in PEG (e.g., a / b) Example: • First invoke the parser a • If succeeded, return a's result • Otherwise, invoke the parser b • If succeeded, return b's result 17 a / b

Slide 19

Slide 19 text

Parser literals Symbol • Parser for nonterminal symbols • e.g., :additive, :multitive String • Parser for terminal symbols • e.g., "+", "lambda" Range • Parser for characters within the specified range • e.g., ?0..?9, ?a..?z 18

Slide 20

Slide 20 text

Implementation of parser literals 19 refine Object do def bind(&block) to_parser.bind(&block) end def to_parser raise TypeError end end

Slide 21

Slide 21 text

Implementation of parser literals (cont'd) 20 refine Symbol do def to_parser Grammar::Parser.new(&self) end end refine String do def to_parser Grammar::Parsers.string(self) end end

Slide 22

Slide 22 text

How does it work? 21 using RaddDjur::DSL "-".bind { :digits.bind { |x| ret -x.to_i } } "-".to_parser.bind { :digits.to_parser.bind { |x| ret -x.to_i } }

Slide 23

Slide 23 text

Pros/cons of refinements Pros • Intuitive notation for DSL (like monkey patching) • Isolation (unlike monkey patching) Cons • High context (unlike monkey patching) 22

Slide 24

Slide 24 text

A limitation of refinements Only file-scoped refinement activation • using is necessary for each file • Incompatible refinements can't be used in the same file 23 using RaddDjur::DSL g = RaddDjur::Grammar.new(:additive) { ... }

Slide 25

Slide 25 text

How to refine refinements Block-scoped refinement activation • How to achieve this? 24 g = RaddDjur::Grammar.new(:additive) { # RaddDjur::DSL is activated here. } x = Foo.new { # another incompatible refinement is # activated here. }

Slide 26

Slide 26 text

PROPOSAL using: option of instance_eval Example • The value of using: option is a module, or an array of module • The refinements defined in the module(s) are activated only in the given block 25 def initialize(&block) instance_eval(using: RaddDjur::DSL, &block) end

Slide 27

Slide 27 text

Considerations Why instance_eval? • instance_eval is often used for DSL • self switching and refinement activation should be able to be used at the same time How about other eval methods? • I have no idea for eval and module_eval • But, instance_exec should have the using: option Readability • Implicit refinement activation may be confusing for users Performance issue • Please help me, SASADA-san 26

Slide 28

Slide 28 text

Conclusion What are refinements for? • For better compatibility • For internal DSL How to refine refinements • using: option for instance_eval 27

Slide 29

Slide 29 text

Thank you! Any questions? 28