Agdaでかけ算の交換法則をDay2-answer.agdaを参考に書きました。というかそのまんま。名前はProgramming Language Foundations in Agdaに合わせたつもりです。理解のためになるべくていねいに書こうと思ったんですが、*-suc
と*-comm
をばらそうとするとエラーになってしまってめんどくさくなってしまいました。*-suc
は先にlemmaが必要なんだろうな。*-comm
は「あっているはずなのに」と思うんですが通らない。基本がわかってないんですよね。refl
はたんに計算するだけで、rewrite
はsuc n
をn
にしたらいいのかと思うんですけど。まあ、いちおうC-c, C-l
で通ります。
import Relation.Binary.PropositionalEquality as Eq
open Eq using (_≡_; refl; cong; sym)
open Eq.≡-Reasoning using (begin_; _≡⟨⟩_; step-≡; _∎)
infixl 6 _+_
infixl 7 _*_
data ℕ : Set where
zero : ℕ
suc : ℕ → ℕ
_+_ : ℕ → ℕ → ℕ
zero + n = n
(suc m) + n = suc (m + n)
+-assoc : ∀ (m n p : ℕ) → (m + n) + p ≡ m + (n + p)
+-assoc zero n p =
begin
(zero + n) + p
≡⟨⟩
n + p
≡⟨⟩
zero + (n + p)
∎
+-assoc (suc m) n p =
begin
(suc m + n) + p
≡⟨⟩
suc (m + n) + p
≡⟨⟩
suc ((m + n) + p)
≡⟨ cong suc (+-assoc m n p) ⟩
suc (m + (n + p))
≡⟨⟩
suc m + (n + p)
∎
+-identityʳ : ∀ (m : ℕ) → m + zero ≡ m
+-identityʳ zero =
begin
zero + zero
≡⟨⟩
zero
∎
+-identityʳ (suc m) =
begin
suc m + zero
≡⟨⟩
suc (m + zero)
≡⟨ cong suc (+-identityʳ m) ⟩
suc m
∎
+-suc : ∀ (m n : ℕ) → m + suc n ≡ suc (m + n)
+-suc zero n =
begin
zero + suc n
≡⟨⟩
suc n
≡⟨⟩
suc (zero + n)
∎
+-suc (suc m) n =
begin
suc m + suc n
≡⟨⟩
suc (m + suc n)
≡⟨ cong suc (+-suc m n) ⟩
suc (suc (m + n))
≡⟨⟩
suc (suc m + n)
∎
+-comm : ∀ (m n : ℕ) → m + n ≡ n + m
+-comm m zero =
begin
m + zero
≡⟨ +-identityʳ m ⟩
m
≡⟨⟩
zero + m
∎
+-comm m (suc n) =
begin
m + suc n
≡⟨ +-suc m n ⟩
suc (m + n)
≡⟨ cong suc (+-comm m n) ⟩
suc (n + m)
≡⟨⟩
suc n + m
∎
_*_ : ℕ → ℕ → ℕ
zero * n = zero
(suc m) * n = n + (m * n)
*-identityʳ : ∀ n → n * zero ≡ zero
*-identityʳ zero =
begin
zero * zero
≡⟨⟩
zero
∎
*-identityʳ (suc m) = *-identityʳ m
*-suc : ∀ m n → m * (suc n) ≡ m + m * n
*-suc zero n =
begin
zero * (suc n)
≡⟨⟩
zero + zero * n
∎
*-suc (suc m) n rewrite *-suc m n =
cong suc (
begin
n + (m + m * n)
≡⟨ sym (+-assoc n m _) ⟩
n + m + m * n
≡⟨ cong (λ x → x + m * n) (+-comm n m) ⟩
m + n + m * n
≡⟨ +-assoc m n _ ⟩
m + (n + m * n)
∎
)
*-comm : ∀ n m → n * m ≡ m * n
*-comm zero m = sym (*-identityʳ m)
*-comm (suc n) m rewrite *-suc m n = cong (_+_ m) (*-comm n m)
∨ Source markdown
Agdaでかけ算の交換法則
Agdaでかけ算の交換法則を[Day2-answer.agda](https://github.com/krtx/agda-handson/blob/master/Day2-answer.agda)を参考に書きました。
というかそのまんま。
名前は[Programming Language Foundations in Agda](https://plfa.github.io/Induction/)に合わせたつもりです。
理解のためになるべくていねいに書こうと思ったんですが、`*-suc`と`*-comm`をばらそうとするとエラーになってしまってめんどくさくなってしまいました。
`*-suc`は先にlemmaが必要なんだろうな。
`*-comm`は「あっているはずなのに」と思うんですが通らない。
基本がわかってないんですよね。
`refl`はたんに計算するだけで、`rewrite`は`suc n`を`n`にしたらいいのかと思うんですけど。
まあ、いちおう`C-c, C-l`で通ります。
```
import Relation.Binary.PropositionalEquality as Eq
open Eq using (_≡_; refl; cong; sym)
open Eq.≡-Reasoning using (begin_; _≡⟨⟩_; step-≡; _∎)
infixl 6 _+_
infixl 7 _*_
data ℕ : Set where
zero : ℕ
suc : ℕ → ℕ
_+_ : ℕ → ℕ → ℕ
zero + n = n
(suc m) + n = suc (m + n)
+-assoc : ∀ (m n p : ℕ) → (m + n) + p ≡ m + (n + p)
+-assoc zero n p =
begin
(zero + n) + p
≡⟨⟩
n + p
≡⟨⟩
zero + (n + p)
∎
+-assoc (suc m) n p =
begin
(suc m + n) + p
≡⟨⟩
suc (m + n) + p
≡⟨⟩
suc ((m + n) + p)
≡⟨ cong suc (+-assoc m n p) ⟩
suc (m + (n + p))
≡⟨⟩
suc m + (n + p)
∎
+-identityʳ : ∀ (m : ℕ) → m + zero ≡ m
+-identityʳ zero =
begin
zero + zero
≡⟨⟩
zero
∎
+-identityʳ (suc m) =
begin
suc m + zero
≡⟨⟩
suc (m + zero)
≡⟨ cong suc (+-identityʳ m) ⟩
suc m
∎
+-suc : ∀ (m n : ℕ) → m + suc n ≡ suc (m + n)
+-suc zero n =
begin
zero + suc n
≡⟨⟩
suc n
≡⟨⟩
suc (zero + n)
∎
+-suc (suc m) n =
begin
suc m + suc n
≡⟨⟩
suc (m + suc n)
≡⟨ cong suc (+-suc m n) ⟩
suc (suc (m + n))
≡⟨⟩
suc (suc m + n)
∎
+-comm : ∀ (m n : ℕ) → m + n ≡ n + m
+-comm m zero =
begin
m + zero
≡⟨ +-identityʳ m ⟩
m
≡⟨⟩
zero + m
∎
+-comm m (suc n) =
begin
m + suc n
≡⟨ +-suc m n ⟩
suc (m + n)
≡⟨ cong suc (+-comm m n) ⟩
suc (n + m)
≡⟨⟩
suc n + m
∎
_*_ : ℕ → ℕ → ℕ
zero * n = zero
(suc m) * n = n + (m * n)
*-identityʳ : ∀ n → n * zero ≡ zero
*-identityʳ zero =
begin
zero * zero
≡⟨⟩
zero
∎
*-identityʳ (suc m) = *-identityʳ m
*-suc : ∀ m n → m * (suc n) ≡ m + m * n
*-suc zero n =
begin
zero * (suc n)
≡⟨⟩
zero + zero * n
∎
*-suc (suc m) n rewrite *-suc m n =
cong suc (
begin
n + (m + m * n)
≡⟨ sym (+-assoc n m _) ⟩
n + m + m * n
≡⟨ cong (λ x → x + m * n) (+-comm n m) ⟩
m + n + m * n
≡⟨ +-assoc m n _ ⟩
m + (n + m * n)
∎
)
*-comm : ∀ n m → n * m ≡ m * n
*-comm zero m = sym (*-identityʳ m)
*-comm (suc n) m rewrite *-suc m n = cong (_+_ m) (*-comm n m)
```
∨ Source code
# coding: utf-8
require 'optparse'
require 'open3'
require 'strscan'
$lib = ""
class Context
attr_reader :state
def initialize
@block = Block.new
@list = List.new
@quote = Quote.new
@paragraph = Paragraph.new
@footnote = Footnote.new
@normal = Normal.new
@state = @normal
end
def change_state(state)
if @state != state then
@state.finish
@state = state
@state.start
end
end
def block?
@state.block?
end
def process(line)
@state.process(line)
end
def block(end_str, header)
@block.end_str = end_str
@block.header = header
change_state(@block)
end
def normal
change_state(@normal)
end
def list(content)
change_state(@list)
printf "<li>" << parse_line(content) << "</li>"
end
def quote(content)
change_state(@quote)
puts parse_line(content)
end
def paragraph(line)
change_state(@paragraph)
@paragraph.add_line(parse_line(line))
end
def footnote(tag, content)
change_state(@footnote)
printf "<li><a id='footnote_#{tag}'>[#{tag}]</a>:" << parse_line(content) << "</li>"
end
end
class State
def start
printf self.class::HEADER
end
def finish
if self.class::FOOTER.length > 0 then
printf "#{self.class::FOOTER}\n"
end
end
def block?
false
end
end
class Block < State
HEADER = ""
FOOTER = ""
attr_accessor :end_str
attr_accessor :header
attr_accessor :buffer
def initialize
@buffer = ""
end
def start
initialize
end
def process(line)
@buffer = @buffer << line << "\n"
end
def block?
true
end
end
class Normal < State
HEADER = ""
FOOTER = ""
end
class List < State
HEADER = "<ul>"
FOOTER = "</ul>"
end
class Quote < State
HEADER = "<blockquote style='font-size: medium; text-align: left; border-left: 5px solid #DDDDDD; padding-left: 10px; background: #F0F0F0'>"
FOOTER = "</blockquote>"
end
class Paragraph < State
PARAGRAPH_HEADER = "<p>"
PARAGRAPH_FOOTER = "</p>"
DESCRIPTION_LIST_HEADER = "<dl>"
DESCRIPTION_LIST_TERM_HEADER = "<dt>"
DESCRIPTION_LIST_TERM_FOOTER = "</dt>"
DESCRIPTION_LIST_VALUE_HEADER = "<dd>"
DESCRIPTION_LIST_VALUE_FOOTER = "</dd>"
DESCRIPTION_LIST_FOOTER = "</dl>"
def initialize
@lines = []
@mode = :paragraph
end
def start
initialize
end
def add_line(line)
if md = line.match(/^: (.*)/) then
@mode = :deflist
end
@lines.push(line)
end
def finish
if @mode == :paragraph then
print PARAGRAPH_HEADER
@lines.each do |line|
print line
end
puts PARAGRAPH_FOOTER
else
puts DESCRIPTION_LIST_HEADER
@lines.each do |line|
if md = line.match(/^: (.*)/) then
print DESCRIPTION_LIST_VALUE_HEADER
print md[1]
puts DESCRIPTION_LIST_VALUE_FOOTER
else
print DESCRIPTION_LIST_TERM_HEADER
print line
puts DESCRIPTION_LIST_TERM_FOOTER
end
end
puts DESCRIPTION_LIST_FOOTER
end
end
end
class Footnote < State
HEADER = "<ul>"
FOOTER = "</ul>"
end
mathjax = <<RUBY
<script>
MathJax = {
chtml: {
displayAlign: "left",
},
tex: {
inlineMath: [['$', '$']]
}
};
</script>
<script type="text/javascript" id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">
</script>
RUBY
header = <<-RUBY
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<script>
MathJax = {
chtml: {
displayAlign: "left",
},
tex: {
inlineMath: [['$', '$']]
}
};
</script>
<script type="text/javascript" id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">
</script>
<title>TITLE</title>
</head>
<body>
<h1>TITLE</h1>
RUBY
footer = <<RUBY
</body>
</html>
RUBY
opt = OptionParser.new
type = :html
opt.on('-b', '--blog', 'blog type output') do |v|
type = :blog
end
filename = ""
opt.parse!(ARGV)
if ARGV.size > 1 then
STDERR.puts "Too many argument: ARGV=#{ARGV}"
exit 1
end
filename = ARGV[0]
if !File.exists?(filename) then
STDERR.puts "File not exists: ARGV=#{ARGV}"
exit 1
end
def parse_line(line)
line.gsub!(/`([^`]*)`/) {
s = $1
s.gsub!('&', "&")
s.gsub!('<', "<")
s.gsub!('>', ">")
"<code style='padding: 2px 3px ; background: #FEFEFE; border: 1px solid #DDDDDD; border-radius: 5px'>#{s}</code>"
}
line.gsub!(/\[([^\]]*)\]\(([^\)]*)\)/) {
"<a href='#{$2}'>#{$1}</a>"
}
line.gsub!(/\[\^([^\]]*)\]/) {
"<a href='#footnote_#{$1}'><sup>[#{$1}]</sup></a>"
}
line
end
def parse_option(str)
lang = nil
name = nil
options = {"echo"=>true, "eval"=>true, "include"=>true}
s = StringScanner.new(str)
lang = s.scan(/\w+/)
s.scan(/\s+/)
name = s.scan(/\w+/)
while !s.eos?
s.scan(/ *, */)
option = s.scan(/\w+/)
s.scan(/ *= */)
value = s.scan(/\w+/)
if value.casecmp("false") == 0 then
options[option] = false
end
end
return lang, name, options
end
def process_block(block)
lang = nil
name = nil
options = {"echo"=>true, "eval"=>false, "include"=>true}
if md = block.header.match(/^ *([^{: ]+)(:([^ ]*))?/) then
lang = md[1]
name = md[3]
options = {"echo"=>true, "eval"=>false, "include"=>true}
elsif md = block.header.match(/^ *{(.*)}/) then
lang, name, options = parse_option(md[1])
end
if lang == "lib" then
$lib = $lib << "\n" << block.buffer
end
if options["include"] then
print '<div style="margin: 0 0 10px 0;">'
if options["echo"] then
if !name.nil? then
printf "<div style=\"border: 1px solid black;padding-left: 3px;margin-bottom: 0;\">#{name}</div>"
end
print '<pre style="overflow-x: scroll;padding: 1px 1px 1px 3px;border:1px solid black;margin: 0;"><code>'
buffer = block.buffer.gsub('&', "&")
buffer.gsub!('<', "<")
buffer.gsub!('>', ">")
puts buffer
puts "</code></pre>"
end
if options["eval"] then
print '<div style="overflow-x: scroll;padding: 1px 1px 1px 3px;border:1px solid black;margin-top: 0;">'
if lang == "math" then
print "$$"
print block.buffer
print "$$"
elsif lang == "ruby" then
print "<pre>"
print eval($lib + block.buffer)
print "</pre>"
elsif lang == "PlantUML" then
Open3.popen3("java -jar /mnt/d/app/plantuml.jar -pipe -svg -charset UTF-8") do |i, o, e, w|
i.write block.buffer
i.close
o.each do |l| puts l end
e.each do |l| printf("<!-- stderr: %s -->\n", l) end
printf("<!-- thread: %s -->\n", w.value)
end
end
print "</div>"
end
print '</div>'
end
end
def print_script
puts <<-RUBY
<script>
function toggleDisplay(e) {
if (e.style.display === "none") {
e.style.display = "block";
} else {
e.style.display = "none";
}
};
function toggleDisplayMark(e) {
str = e.innerText;
c = str.substring(0, 1);
if (c === "∨") {
e.innerText = "∧" + str.substring(1, str.length);
} else if (c === "∧") {
e.innerText = "∨" + str.substring(1, str.length);
}
}
</script>
RUBY
end
def print_code(filename, title)
puts %!<div onclick="toggleDisplayMark(this);toggleDisplay(this.nextElementSibling);" style="margin-top: 10px; border: 1px solid black;">∨ #{title}</div>!
print '<pre style="display: none;overflow-x: scroll;padding: 1px 1px 1px 3px;border:1px solid black;margin: 0;"><code>'
File.open(filename) do |f|
f.each_line do |line|
line.gsub!('&', "&")
line.gsub!('<', "<")
line.gsub!('>', ">")
puts line
end
puts "</code></pre>"
end
end
File.open(filename, "r") do |i|
context = Context.new
i.each_line do |line|
line.chomp!
if i.lineno == 1 then
if type == :html then
puts header.gsub(/TITLE/, line)
else
puts line
puts mathjax
end
elsif context.block? then
re = Regexp.new("^#{context.state.end_str}")
if line.match(re) then
process_block(context.state)
context.normal
else
context.process(line)
end
elsif md = line.match(/^(`{3,})(.*)/) then
end_str = md[1]
header = md[2]
context.block(end_str, header)
elsif md = line.match(/^(#+) *(.*)/) then
level = md[1].length + 1
printf("<h#{level}>%s</h#{level}>\n", md[2])
elsif md = line.match(/^[ \t]*$/) then
context.normal
elsif md = line.match(/^> (.*)/) then
context.quote(md[1])
elsif md = line.match(/^- (.*)/) then
context.list(md[1])
elsif md = line.match(/^\[\^([^\]]*)\]:(.*)/) then
context.footnote(md[1], md[2])
elsif md = line.match(/^! *include +(.*)/) then
context.normal
file = File.open(md[1])
puts file.read
else
context.paragraph(line)
end
end
context.normal
end
print_script
print_code filename, "Source markdown"
print_code __FILE__, "Source code"
if type == :html then
puts(footer)
end
コメント
コメントを投稿