From 8f58222e6cae5b5aefe89283e742cc1c2a015af9 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 17 May 2019 20:39:50 -0500 Subject: [PATCH] Refactor builtins The fill/unfill code is much simpler now --- lib/dhall/ast.rb | 2 +- lib/dhall/builtins.rb | 403 +++++++++++++++++------------------------ lib/dhall/normalize.rb | 2 +- lib/dhall/typecheck.rb | 11 +- 4 files changed, 170 insertions(+), 248 deletions(-) diff --git a/lib/dhall/ast.rb b/lib/dhall/ast.rb index 5681541..2194b0d 100644 --- a/lib/dhall/ast.rb +++ b/lib/dhall/ast.rb @@ -128,7 +128,7 @@ module Dhall def flatten f, args = if function.is_a?(Application) function.flatten - elsif function.is_a?(Builtin) && + elsif function.is_a?(BuiltinFunction) && (unfilled = function.unfill).is_a?(Application) unfilled.flatten else diff --git a/lib/dhall/builtins.rb b/lib/dhall/builtins.rb index 91dfa36..e448c17 100644 --- a/lib/dhall/builtins.rb +++ b/lib/dhall/builtins.rb @@ -6,93 +6,74 @@ module Dhall class Builtin < Expression include(ValueSemantics.for_attributes {}) - def call(*args) - # Do not auto-normalize builtins to avoid recursion loop - args.reduce(self) do |f, arg| + def as_json + self.class.name&.split(/::/)&.last&.tr("_", "/").to_s + end + end + + class BuiltinFunction < Builtin + include(ValueSemantics.for_attributes do + partial_application ArrayOf(Expression), default: [] + end) + + def unfill(*args) + (args.empty? ? partial_application : args).reduce(self.class.new) do |f, arg| Application.new(function: f, argument: arg) end end - def unfill - attributes.reduce(self.class.new) do |f, attr| - if send(attr.name).nil? - f - else - Application.new(function: f, argument: send(attr.name)) - end + def call(*new_args) + args = partial_application + new_args + if args.length == method(:uncurried_call).arity + uncurried_call(*args) + else + with(partial_application: args) end end def as_json - if (unfilled = unfill).class != self.class + if (unfilled = unfill) != self unfilled.as_json else - self.class.name&.split(/::/)&.last&.tr("_", "/").to_s + super end end + end - protected + module Builtins + # rubocop:disable Style/ClassAndModuleCamelCase - def attributes - self.class.value_semantics.attributes - end + class Double_show < BuiltinFunction + protected - def fill_next_if_valid(value) - with(attributes.each_with_object({}) do |attr, h| - if !send(attr.name).nil? - h[attr.name] = send(attr.name) - elsif attr.validate?(value) - h[attr.name] = value - value = nil - else - return nil - end - end) - end + def uncurried_call(arg) + return unfill(arg) unless arg.is_a?(Dhall::Double) - def full? - attributes.all? { |attr| !send(attr.name).nil? } + Dhall::Text.new(value: arg.to_s) + end end - def fill_or_call(arg, &block) - full? ? block[arg] : fill_next_if_valid(arg) - end - end + class Integer_show < BuiltinFunction + protected - module Builtins - # rubocop:disable Style/ClassAndModuleCamelCase + def uncurried_call(arg) + return unfill(arg) unless arg.is_a?(Dhall::Integer) - class Double_show < Builtin - def call(arg) - if arg.is_a?(Dhall::Double) - Dhall::Text.new(value: arg.to_s) - else - super - end + Dhall::Text.new(value: arg.to_s) end end - class Integer_show < Builtin - def call(arg) - if arg.is_a?(Dhall::Integer) - Dhall::Text.new(value: arg.to_s) - else - super - end - end - end + class Integer_toDouble < BuiltinFunction + protected - class Integer_toDouble < Builtin - def call(arg) - if arg.is_a?(Dhall::Integer) - Dhall::Double.new(value: arg.value.to_f) - else - super - end + def uncurried_call(arg) + return unfill(arg) unless arg.is_a?(Dhall::Integer) + + Dhall::Double.new(value: arg.value.to_f) end end - class Natural_build < Builtin + class Natural_build < BuiltinFunction def fusion(arg, *bogus) if bogus.empty? && arg.is_a?(Application) && @@ -103,7 +84,9 @@ module Dhall end end - def call(arg) + protected + + def uncurried_call(arg) arg.call( Natural.new, Function.of_arguments( @@ -115,79 +98,71 @@ module Dhall end end - class Natural_even < Builtin - def call(nat) - if nat.is_a?(Dhall::Natural) - Dhall::Bool.new(value: nat.even?) - else - super - end - end - end + class Natural_even < BuiltinFunction + protected - class Natural_fold < Builtin - include(ValueSemantics.for_attributes do - nat Either(nil, Dhall::Natural), default: nil - type Either(nil, Expression), default: nil - f Either(nil, Expression), default: nil - end) + def uncurried_call(nat) + return unfill(nat) unless nat.is_a?(Dhall::Natural) - def call(arg) - fill_or_call(arg) do - if @nat.zero? - arg.normalize - else - @f.call(with(nat: nat.pred).call(arg)) - end - end || super + Dhall::Bool.new(value: nat.even?) end end - class Natural_isZero < Builtin - def call(nat) - if nat.is_a?(Dhall::Natural) - Dhall::Bool.new(value: nat.zero?) + class Natural_fold < BuiltinFunction + protected + + def uncurried_call(nat, type, f, z) + return unfill(nat, type, f, z) unless nat.is_a?(Dhall::Natural) + + if nat.zero? + z.normalize else - super + f.call(Natural_fold.new.call(nat.pred, type, f, z)) end end end - class Natural_odd < Builtin - def call(nat) - if nat.is_a?(Dhall::Natural) - Dhall::Bool.new(value: nat.odd?) - else - super - end + class Natural_isZero < BuiltinFunction + protected + + def uncurried_call(nat) + return unfill(nat) unless nat.is_a?(Dhall::Natural) + + Dhall::Bool.new(value: nat.zero?) end end - class Natural_show < Builtin - def call(nat) - if nat.is_a?(Dhall::Natural) - Dhall::Text.new(value: nat.to_s) - else - super - end + class Natural_odd < BuiltinFunction + protected + + def uncurried_call(nat) + return unfill(nat) unless nat.is_a?(Dhall::Natural) + + Dhall::Bool.new(value: nat.odd?) end end - class Natural_toInteger < Builtin - def call(nat) - if nat.is_a?(Dhall::Natural) - Dhall::Integer.new(value: nat.value) - else - super - end + class Natural_show < BuiltinFunction + protected + + def uncurried_call(nat) + return unfill(nat) unless nat.is_a?(Dhall::Natural) + + Dhall::Text.new(value: nat.to_s) end end - class List_build < Builtin - include(ValueSemantics.for_attributes do - type Either(nil, Expression), default: nil - end) + class Natural_toInteger < BuiltinFunction + protected + + def uncurried_call(nat) + return unfill(nat) unless nat.is_a?(Dhall::Natural) + + Dhall::Integer.new(value: nat.value) + end + end + class List_build < BuiltinFunction def fusion(*args) _, arg, = args if arg.is_a?(Application) && @@ -199,19 +174,17 @@ module Dhall end end - def call(arg) - fill_or_call(arg) do - arg.call( - List.new.call(type), - cons, - EmptyList.new(element_type: type) - ) - end - end - protected - def cons + def uncurried_call(type, arg) + arg.call( + List.new.call(type), + cons(type), + EmptyList.new(element_type: type) + ) + end + + def cons(type) Function.of_arguments( type, List.new.call(type.shift(1, "_", 0)), @@ -220,56 +193,33 @@ module Dhall end end - class List_fold < Builtin - include(ValueSemantics.for_attributes do - ltype Either(nil, Expression), default: nil - list Either(nil, List), default: nil - ztype Either(nil, Expression), default: nil - f Either(nil, Expression), default: nil - end) - - def call(arg) - fill_or_call(arg) do - list.reduce(arg, &f).normalize - end || super - end - end + class List_fold < BuiltinFunction + protected - class List_head < Builtin - include(ValueSemantics.for_attributes do - type Either(nil, Expression), default: nil - end) + def uncurried_call(ltype, list, ztype, f, z) + return unfill(ltype, list, ztype, f, z) unless list.is_a?(Dhall::List) - def call(arg) - fill_or_call(arg) do - if arg.is_a?(Dhall::List) - arg.first - else - super - end - end + list.reduce(z, &f).normalize end end - class List_indexed < Builtin - include(ValueSemantics.for_attributes do - type Either(nil, Expression), default: nil - end) + class List_head < BuiltinFunction + protected - def call(arg) - fill_or_call(arg) do - if arg.is_a?(Dhall::List) - _call(arg) - else - super - end - end + def uncurried_call(type, list) + return unfill(type, list) unless list.is_a?(Dhall::List) + + list.first end + end + class List_indexed < BuiltinFunction protected - def _call(arg) - arg.map(type: indexed_type(type)) { |x, idx| + def uncurried_call(type, list) + return unfill(type, list) unless list.is_a?(Dhall::List) + + list.map(type: indexed_type(type)) { |x, idx| Record.new( record: { "index" => Dhall::Natural.new(value: idx), @@ -289,59 +239,37 @@ module Dhall end end - class List_last < Builtin - include(ValueSemantics.for_attributes do - type Either(nil, Expression), default: nil - end) + class List_last < BuiltinFunction + protected - def call(arg) - fill_or_call(arg) do - if arg.is_a?(Dhall::List) - arg.last - else - super - end - end + def uncurried_call(type, list) + return unfill(type, list) unless list.is_a?(Dhall::List) + + list.last end end - class List_length < Builtin - include(ValueSemantics.for_attributes do - type Either(nil, Expression), default: nil - end) + class List_length < BuiltinFunction + protected - def call(arg) - fill_or_call(arg) do - if arg.is_a?(Dhall::List) - Dhall::Natural.new(value: arg.length) - else - super - end - end + def uncurried_call(type, list) + return unfill(type, list) unless list.is_a?(Dhall::List) + + Dhall::Natural.new(value: list.length) end end - class List_reverse < Builtin - include(ValueSemantics.for_attributes do - type Either(nil, Expression), default: nil - end) + class List_reverse < BuiltinFunction + protected - def call(arg) - fill_or_call(arg) do - if arg.is_a?(Dhall::List) - arg.reverse - else - super - end - end + def uncurried_call(type, list) + return unfill(type, list) unless list.is_a?(Dhall::List) + + list.reverse end end - class Optional_build < Builtin - include(ValueSemantics.for_attributes do - type Either(nil, Expression), default: nil - end) - + class Optional_build < BuiltinFunction def fusion(*args) _, arg, = args if arg.is_a?(Application) && @@ -353,19 +281,17 @@ module Dhall end end - def call(arg) - fill_or_call(arg) do - arg.call( - Optional.new.call(type), - some, - OptionalNone.new(value_type: type) - ) - end - end - protected - def some + def uncurried_call(type, f) + f.call( + Optional.new.call(type), + some(type), + OptionalNone.new(value_type: type) + ) + end + + def some(type) Function.of_arguments( type, body: Dhall::Optional.new( @@ -376,24 +302,19 @@ module Dhall end end - class Optional_fold < Builtin - include(ValueSemantics.for_attributes do - type Either(nil, Expression), default: nil - optional Either(nil, Optional), default: nil - ztype Either(nil, Expression), default: nil - f Either(nil, Expression), default: nil - end) + class Optional_fold < BuiltinFunction + protected - def call(*args) - args.reduce(self) do |fold, arg| - fold.fill_or_call(arg) do - fold.optional.reduce(arg, &fold.f) - end || super + def uncurried_call(type, optional, ztype, f, z) + unless optional.is_a?(Dhall::Optional) + return unfill(type, optional, ztype, f, z) end + + optional.reduce(z, &f) end end - class Text_show < Builtin + class Text_show < BuiltinFunction ENCODE = (Hash.new { |_, x| "\\u%04x" % x.ord }).merge( "\"" => "\\\"", "\\" => "\\\\", @@ -404,17 +325,17 @@ module Dhall "\t" => "\\t" ) - def call(arg) - if arg.is_a?(Dhall::Text) - Dhall::Text.new( - value: "\"#{arg.value.gsub( - /["\$\\\b\f\n\r\t\u0000-\u001F]/, - &ENCODE - )}\"" - ) - else - super - end + protected + + def uncurried_call(text) + return unfill(text) unless text.is_a?(Dhall::Text) + + Dhall::Text.new( + value: "\"#{text.to_s.gsub( + /["\$\\\b\f\n\r\t\u0000-\u001F]/, + &ENCODE + )}\"" + ) end end diff --git a/lib/dhall/normalize.rb b/lib/dhall/normalize.rb index 0d82b1c..d04a64e 100644 --- a/lib/dhall/normalize.rb +++ b/lib/dhall/normalize.rb @@ -51,7 +51,7 @@ module Dhall normalized = super return normalized.fuse if normalized.fuse - if normalized.function.is_a?(Builtin) || + if normalized.function.is_a?(BuiltinFunction) || normalized.function.is_a?(Function) || normalized.function.is_a?(RecordSelection) return normalized.function.call(normalized.argument) diff --git a/lib/dhall/typecheck.rb b/lib/dhall/typecheck.rb index 3aab49a..b7b6b3e 100644 --- a/lib/dhall/typecheck.rb +++ b/lib/dhall/typecheck.rb @@ -1209,12 +1209,13 @@ module Dhall TypeChecker.register self, Dhall::Builtin def self.for(builtin) - unfilled = builtin.unfill - if unfilled != builtin - TypeChecker.for(unfilled) - else - new(builtin) + if builtin.is_a?(Dhall::BuiltinFunction) + if (unfilled = builtin.unfill) != builtin + return TypeChecker.for(unfilled) + end end + + new(builtin) end def initialize(builtin) -- 2.38.5