Update: add Prolog translation, 4A system design, Mermaid, Hextra partials & blog articles
This commit is contained in:
+6
-6
@@ -2,14 +2,14 @@
|
||||
title: FuKun
|
||||
layout: hextra-home
|
||||
---
|
||||
{{< hextra/hero-subtitle >}}
|
||||
{{< hextra-hero-subtitle >}}
|
||||
Fukun.Net
|
||||
{{< /hextra/hero-subtitle >}}
|
||||
{{< /hextra-hero-subtitle >}}
|
||||
|
||||
{{< hextra/hero-headline >}}
|
||||
{{< hextra-hero-headline >}}
|
||||
晴耕雨读 心灯不夜
|
||||
{{< /hextra/hero-headline >}}
|
||||
{{< /hextra-hero-headline >}}
|
||||
|
||||
{{< hextra/hero-subtitle >}}
|
||||
{{< hextra-hero-subtitle >}}
|
||||
一周更新两次,让脑袋泡泡冰浴
|
||||
{{< /hextra/hero-subtitle >}}
|
||||
{{< /hextra-hero-subtitle >}}
|
||||
|
||||
+16
-8
@@ -1,23 +1,31 @@
|
||||
---
|
||||
---
|
||||
{{< hextra/hero-subtitle >}}
|
||||
{{< hextra-hero-subtitle >}}
|
||||
Fukun.Net/about
|
||||
{{< /hextra/hero-subtitle >}}
|
||||
{{< /hextra-hero-subtitle >}}
|
||||
|
||||
我是傅堃。
|
||||
|
||||
个人开发者,央企TL,新晋奶爸。当然大部分时间精力贡献给了体制内职业发展,IT运维智能化、营业计费系统建设、大规模IT资源池安全能力建设、企业身份账号权限治理,挺有意思的。
|
||||
央企TL,个人开发者,新晋奶爸。当然大部分时间精力还是贡献给了体制内的职业发展,干过IT运维智能化、营业计费系统建设、大规模IT资源池安全能力建设、企业身份账号权限治理,挺有意思的。
|
||||
|
||||
这个站点与大家探讨的重点不是道与术,更多是去探索**自我**的存在。人其实很难承认被所谓靠努力成为的角色所定义,你我在本质上与小时候有多大区别呢。如果你也想在日常的繁杂中找一个属于自我的空间,挑战落地一些奇思妙想,或是研究一些稀奇古怪,这里的文章会给你启发。
|
||||
这个站点主要讨论的不是道与术,是本应凌驾于身份角色之上的**自我**。总觉得人其实很难承认自己被所谓靠自己努力成为的身份角色定义,从出生起人的单一身份很难未维持超过5年,情绪有所触动时与五岁时其实没多大区别。
|
||||
|
||||
由于公司管理制度,我不在业余取酬,不回复商务合作邮件。
|
||||
如果你也想在日常的繁杂中找一个属于自我的空间,挑战落地一些奇思妙想,或是研究一些稀奇古怪,这里的文章会给你启发。
|
||||
|
||||
由于公司管理制度,我不可以参与盈利项目,所以无法接洽商务合作。
|
||||
|
||||
|
||||
驾驶教练机拉杆起飞的那一刻/宇治城的小溪边被斜阳刺痛双眼的那一刻/在广汕国道上骑自行车听到路旁大爷大喊加油的那一刻/我清楚的感受到我是我自己
|
||||
|
||||
|
||||
Hey, I'm FuKun.
|
||||
|
||||
Solo developer. IT Team-leader at a state-owned enterprise. New dad. Most of my time and energy goes to my career — AIOPS, CRM, IAM, Security,Identity governance. It's been genuinely interesting.
|
||||
Solo developer. Ex-product manager. China state-owned enterprise manager. New dad. Most of my waking hours go to my career — I've done IT operations automation at scale, built billing systems handling massive transaction volumes, architected security for enormous resource pools, and untangled enterprise identity governance. It's been fun.
|
||||
|
||||
This site isn't only about methodology or technique. It's about exploring the **SOUL** that exists beneath all the roles. It's hard to admit how much we're defined by what we've fought to become — honestly, are you really that different from who you were as a kid? If you're also trying to carve out space for your own self amid the chaos, chasing down wild ideas or researching obscure things, the writing here might spark something.
|
||||
This site isn't about methodology or technique. It's about the **self** that should sit above all the roles we play. I think people struggle to admit how much their identity gets defined by their roles — and yet, no identity you've held has lasted more than five years since birth. When emotions hit, you still react the same way you did at five.
|
||||
|
||||
Due to company policy, I don't take paid work on the side and don't reply to business collaboration emails.
|
||||
If you're trying to carve out space for your own self amid the chaos — chasing down a wild idea, researching something obscure, building something that's just yours — the writing here might spark something.
|
||||
|
||||
A note on logistics: due to company policy, I can't participate in for-profit projects, so I don't take on business collaborations.
|
||||
|
||||
The moment when I pulled back the Cessena's stick. The moment setting sun stinged my eyes by a stream in Uji.The moment an old sir shouted "COME ON!" as I was crossing South-China by bike. Without question, that I was aware WHOIAM.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
linkTitle: FuKun.Net/Blog
|
||||
---
|
||||
{{< hextra/hero-subtitle >}}
|
||||
{{< hextra-hero-subtitle >}}
|
||||
Fukun.Net/blog
|
||||
{{< /hextra/hero-subtitle >}}
|
||||
{{< /hextra-hero-subtitle >}}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
title: "善假说"
|
||||
date: 2026-05-16T01:30:00+08:00
|
||||
draft: false
|
||||
poet: |
|
||||
君子生非异也,
|
||||
善假于物也。
|
||||
——《荀子·劝学》
|
||||
description: "近以AI智能体为幕僚,五日亲历,颇有所得。录之以证荀子之言。"
|
||||
tags:
|
||||
- AI
|
||||
- 效率
|
||||
- 杂文
|
||||
---
|
||||
|
||||
{{< figure src="/images/fusheng.jpg" caption="杜堇《伏生授经图》,明,大都会艺术博物馆藏" alt="杜堇《伏生授经图》" >}}
|
||||
|
||||
荀子说,君子生非异也,善假于物也。我以前读完也就过了,没细想。
|
||||
|
||||
AI 这两年用得不少,ChatGPT、Claude、DeepSeek 都试过。问问题、写代码、润文字,确实方便。但模式始终是单向的——它等你问,你问它答,问完结束。
|
||||
|
||||
直到我用上一个能自己干活的智能体。它叫"爪子",跑在腾讯云一台轻量服务器上,4 核 4G,配置不高。搭起来很简单:在服务器上装了一个叫 OpenClaw 的智能体框架,绑好微信,配上 Todoist 和 Git 的 API 密钥,就能远程使唤了。这东西驻扎在服务器上,能直接执行 Shell 命令、读写文件、操作 Git 仓库、浏览网页。从有想法到跑起来,花了半晚上。
|
||||
|
||||
## 役物和任人
|
||||
|
||||
同样是 AI,心态不同,用法天差地别。
|
||||
|
||||
**役物**——问一句答一句,答错了你纠,纠完再问。你是指挥,还得盯着每一步。
|
||||
|
||||
**任人**——说清楚方向,交给它去做。做完验收就行,中间过程不必过问。
|
||||
|
||||
前者把你绑在操作台前,后者让你从执行中抽身。区别不在技术,在心法。
|
||||
|
||||
## 三件事
|
||||
|
||||
试了五天,挑三件说说。
|
||||
|
||||
### 博客
|
||||
|
||||
午间忽想建站,交代了一句。它 SSH 上服务器就开始干活。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[我:建个博客] --> B[SSH 登录服务器]
|
||||
B --> C[安装 Hugo + Nginx]
|
||||
C --> D[配置 HTTPS]
|
||||
D --> E{测试通过?}
|
||||
E -->|否| F[自修权限问题]
|
||||
F --> D
|
||||
E -->|是| G[回复就绪]
|
||||
```
|
||||
|
||||
装 Hugo、配 Nginx、设 HTTPS、修 404 错误,全是它自己弄的。后来主题换了三次——PaperMod、terminal、Hextra——都是半夜一句话的事。第二天醒来网站已经跑在新主题上了。
|
||||
|
||||
### 资讯推送
|
||||
|
||||
每天早上想看国际新闻、科技动态和通信消息。我让它写了个定时脚本,每天 8:30 推送微信。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Cron 8:30] --> B[拉取待办 + 新闻]
|
||||
B --> C[编译简报]
|
||||
C --> D[推送微信]
|
||||
D --> E{成功?}
|
||||
E -->|失败| F[自查超时]
|
||||
F --> G[延长时限重发]
|
||||
G --> D
|
||||
E -->|成功| H[记入日志]
|
||||
```
|
||||
|
||||
第三天推送没来。没等我问,它已经查了日志,发现 DeepSeek API 响应变慢导致超时,自动调长时限重发了。
|
||||
|
||||
### 工单管理
|
||||
|
||||
工作对话截图丢过去,说一句"入待办"。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[我:发聊天截图] --> B[读取并提取信息]
|
||||
B --> C[创建 Todoist 任务]
|
||||
C --> D[标优先级 + 截止日]
|
||||
D --> E[回复确认]
|
||||
```
|
||||
|
||||
读图、识人、提取待办、写入 Todoist、标优先级、定截止期。原来要折腾一分钟的事,现在十秒。
|
||||
|
||||
## 信任是试出来的
|
||||
|
||||
一开始不放心,只让它看看日志、查查状态。后来发现出错能自愈、操作有日志可查、半夜发消息也能秒回——慢慢就放手了。
|
||||
|
||||
**邮件过滤。** 信任更进一步,我给了它 IMAP 权限,让它每天扫收件箱。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[每日扫描收件箱] --> B{发给我但<br>没给下属?}
|
||||
B -->|是| C[建文件夹存档]
|
||||
C --> D[附件存 / 正文转 md]
|
||||
D --> E[撰写摘要]
|
||||
E --> F[关联已有文件信息]
|
||||
F --> G[入待办跟进]
|
||||
B -->|否| H[跳过]
|
||||
```
|
||||
|
||||
筛出发给我但没抄送下属的邮件,拆附件、正文转 markdown、写摘要、关联已有资料,分类归档到我本地工作目录的 inbox。第二天上班打开文件夹,摘要和附件已经摆好了。
|
||||
|
||||
## 写在最后
|
||||
|
||||
有人问:这和 ChatGPT 有什么区别?
|
||||
|
||||
我说:**ChatGPT 是字典,这是秘书。** 字典再全,不会替你写材料。秘书不一定比你懂行,但会替你跑腿。
|
||||
|
||||
需要什么投入?一台低配云服务器,一套 API 凭证,花半晚上搭环境。门槛不高。
|
||||
|
||||
回头看荀子那句话——"善假于物"。最好的工具不是功能最强的,而是能让你从琐事中脱身的。用到某个程度你会发现,真正费脑子的不是"怎么问 AI",而是"什么可以交给 AI"。
|
||||
|
||||
这念头一转,效率的瓶颈就从工具变成了自己的想象力。
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: "AI 生产力的唯一落点"
|
||||
date: 2026-05-17T18:10:00+08:00
|
||||
draft: false
|
||||
poet: |
|
||||
We are stuck with technology when what we really want is just stuff that works.
|
||||
— Douglas Adams, UK, 1952–2001
|
||||
description: "和炎明吃饭聊起 AI 实际应用,感叹一个事实:到今天为止,AI 真正有生产力的应用场景只有 coding 一项。"
|
||||
tags:
|
||||
- AI
|
||||
- 思考
|
||||
---
|
||||
|
||||
晚上和炎明吃饭,聊到 AI 的实际应用。几杯酒下去,话题从宏大叙事落到日常体验。我俩有个共同感受:
|
||||
|
||||
**AI 目前能有生产力的,只有 coding 这一项。**
|
||||
|
||||
这是一个残酷但诚实的判断。
|
||||
|
||||
## 为什么只有 Coding
|
||||
|
||||
仔细想想,Coding 和其他场景有什么本质不同?
|
||||
|
||||
**第一,输出可验证。** 代码跑不跑得通,一运行就知道。结果客观、即时、无歧义。AI 写对了就是对了,错了就是错了,不存在"写得好不好"的主观拉扯。
|
||||
|
||||
**第二,反馈闭环极短。** 写一段代码 → 编译/运行 → 看到结果 → 修改,这个循环可以秒级完成。AI 和开发者之间形成了高频的"对话-验证-迭代"机制。
|
||||
|
||||
**第三,错误可容忍。** 代码写错了不会死人。你可以反复试错,直到跑通为止。这种低代价的试错环境,是 AI 学习和人类协作最舒服的区间。
|
||||
|
||||
**第四,人类判断力最低介入。** 代码的逻辑正确性由机器判定,不需要专家逐行审阅。非技术类任务(写文章、做设计、决策建议)都需要人的判断力做最终把关——这恰恰是最稀缺的。
|
||||
|
||||
## 其他场景为什么不行
|
||||
|
||||
- **写文章** → 需要人类判断风格、语气、事实准确度,改了半个小时不如自己写
|
||||
- **数据分析** → AI 能跑数,但"问对问题"才是关键,而这个恰恰是 AI 做不到的
|
||||
- **创意设计** → 生成一堆选项,人类从中选一个最不差的,本质是降本不是提效
|
||||
- **聊天陪伴** → 有情绪价值,但没有产出,不算生产力
|
||||
|
||||
这些场景的共同问题:**判断成本转移了,但没有消失。** 写文章的时候,AI 帮你省了打字的力气,但审稿的时间一点没少。效率提升只是把体力活变成了脑力活。
|
||||
|
||||
## Coding 也是例外,也是范式
|
||||
|
||||
但仔细想,**未来真正有生产力的 AI 场景,都会呈现出某种"可验证性"**——无论是否在写代码。
|
||||
|
||||
比如合同审查:如果有一套明确的合规规则,AI 可以逐条校验;比如医疗影像:如果有标注好的数据集,AI 可以辅助诊断。这些场景的共同特征都是:**输出可以被客观判定对错。**
|
||||
|
||||
而那些依赖人类主观判断的领域——策略、审美、人情世故——AI 很难产出真正的生产力。不是技术问题,是评价标准的问题。
|
||||
|
||||
## 写在最后
|
||||
|
||||
和炎明的一顿饭,让我更清楚了一件事:**AI 不是万能工具,它是一把专门在"可验证"领域发力的扳手。**
|
||||
|
||||
承认它的边界,比鼓吹它的全能,更有意义。
|
||||
|
||||
而当前这把扳手最趁手的活,就是 coding。这也是为什么从大厂到个人开发者,都在往这个方向猛押注——不是跟风,是因为这里真的能看到产出。
|
||||
@@ -0,0 +1,532 @@
|
||||
---
|
||||
title: "用宝可梦解释 Prolog 基础"
|
||||
date: 2026-05-18T13:30:00+08:00
|
||||
draft: false
|
||||
description: "通过宝可梦对战系统理解逻辑编程语言 Prolog 的核心概念——事实、规则、查询,以及它为什么比 SQL 更灵活。"
|
||||
tags:
|
||||
- Prolog
|
||||
- 宝可梦
|
||||
- 编程语言
|
||||
- 翻译
|
||||
---
|
||||
|
||||
# 用宝可梦解释 Prolog 基础
|
||||
|
||||
2026年1月5日
|
||||
|
||||
启发这篇文章的项目有点傻——我将要详细描述一个儿童电子游戏的机制——但正是这个特定问题最终让我真正理解了 Prolog,这是自从我读了 Bruce Tate 的《七周七语言》以来一直在追寻的顿悟。
|
||||
|
||||
这个练习教会了我很多关于我正在[更实用的领域](https://alexanderpetros.com/triptych/)尝试构建的接口类型。对于某些类型的关系,逻辑编程是我用过的最简洁、最具表现力的编程系统。
|
||||
|
||||
要理解为什么,让我们来聊聊宝可梦(Pokémon)。
|
||||
|
||||
## 宝可梦(Pokémon)基础
|
||||
|
||||
宝可梦是一个电子游戏系列/多媒体特许经营/生活方式品牌,设定在一个人类与各种色彩缤纷的动物角色共存的世界。
|
||||
|
||||
"Pokémon" 既是这个系列的名称,也是这些动物角色本身的统称,每个角色都有自己的物种名称。从 Bulbasaur(妙蛙种子,[#1](https://bulbapedia.bulbagarden.net/wiki/Bulbasaur_(Pok%C3%A9mon)))到 Pecharunt(桃歹郎,[#1025](https://bulbapedia.bulbagarden.net/wiki/Pecharunt_(game))),共有超过一千种不同的宝可梦物种。
|
||||
|
||||
受欢迎的宝可梦包括(从左到右):Pikachu(皮卡丘,[#25](https://bulbapedia.bulbagarden.net/wiki/Pikachu_(Pok%C3%A9mon)))、Archeops(始祖大鸟,[#567](https://bulbapedia.bulbagarden.net/wiki/Archeops_(Pok%C3%A9mon))) 和 Dipplin(裹蜜虫,[#1101](https://bulbapedia.bulbagarden.net/wiki/Dipplin_(Pok%C3%A9mon)))。
|
||||
|
||||
我很少在这个博客里放图片,能在这篇文章里放图让我非常兴奋。
|
||||
|
||||
现在有各种各样的宝可梦游戏,但主系列始终围绕着捕捉和对战。在对战中,你的六只宝可梦队伍与另一支队伍交锋。每只宝可梦配备四个招式,可以选择用来(通常)对对手造成伤害。你需要将对方所有宝可梦的 HP(体力值)减到零,同时防止对方先对你做同样的事。
|
||||
|
||||
每只宝可梦都有独特的特性影响对战表现。它们有一套基础能力值、大量可学的招式、若干特性和属性组合。你马上就会看到,这里庞大的组合数量正是试图用软件来追踪这些信息的动机。
|
||||
|
||||
Scizor(巨钳螳螂)是虫/钢属性,攻击力高但速度低(数据来自 [Smogon](https://www.smogon.com/dex/bw/pokemon/scizor/))
|
||||
|
||||
速度决定哪个招式先出手;攻击和特攻分别影响物理招式和特殊招式的伤害;防御和特防影响受到的伤害。
|
||||
|
||||
属性尤其重要。招式有属性,比如火或岩石,而宝可梦可以拥有最多两种属性。如果招式的属性对对方宝可梦效果绝佳(Super Effective),会造成双倍伤害;效果不佳(Not Very Effective)则只造成一半伤害。
|
||||
|
||||
用例子来说明会更直观一些。火属性的招式喷射火焰(Flamethrower)对草属性宝可梦造成 2 倍伤害,因为草弱火,但水属性的冲浪(Surf)对它们只造成 ½ 伤害,因为草抗水。
|
||||
|
||||
[Lunatone(月石)](https://bulbapedia.bulbagarden.net/wiki/Lunatone_(Pok%C3%A9mon))是岩石/超能力属性。岩石弱水,超能力对水中性,所以冲浪会造成 2 倍伤害。
|
||||
|
||||
属性修正可以叠加。[Scizor(巨钳螳螂)](https://bulbapedia.bulbagarden.net/wiki/Scizor_(Pok%C3%A9mon))是虫/钢属性,虫和钢都弱火,所以火属性招式对 Scizor 会造成 4 倍伤害。电弱水,但地面免疫电,所以如果你对水/地面的 [Swampert(巨沼怪)](https://bulbapedia.bulbagarden.net/wiki/Swampert_(Pok%C3%A9mon))使用电属性招式,伤害为零,因为 0×2 还是 0。
|
||||
|
||||
自然而然地,有一张图表来帮你记忆。
|
||||
|
||||
宝可梦属性相克表(来自 [Wikimedia](https://commons.wikimedia.org/wiki/File:Pokemon_Type_Chart.svg))
|
||||
|
||||
这些基本上就是我 8 岁时理解的宝可梦电子游戏机制。点击招式造成伤害,尽量选择属性相克有利的招式。这些游戏是为儿童设计的,表面上看并不难。
|
||||
|
||||
## Prolog 基础
|
||||
|
||||
在解释宝可梦机制底层有多复杂之前,我需要先解释一下逻辑编程的工作原理。宝可梦非常适合逻辑编程,因为宝可梦对战本质上是一个极其精密的规则引擎。
|
||||
|
||||
让我们先创建一个包含一堆事实的文件。
|
||||
|
||||
```
|
||||
pokemon(bulbasaur).
|
||||
pokemon(ivysaur).
|
||||
pokemon(venusaur).
|
||||
pokemon(charmander).
|
||||
pokemon(charmeleon).
|
||||
pokemon(charizard).
|
||||
pokemon(squirtle).
|
||||
pokemon(wartortle).
|
||||
pokemon(blastoise).
|
||||
```
|
||||
|
||||
在 Prolog 中,我们声明"谓词"(predicates)。谓词定义关系:bulbasaur 是一只宝可梦,charmander 是一只宝可梦,以此类推。我们称这个谓词为 pokemon/1,因为谓词名称是 pokemon,它有一个参数。
|
||||
|
||||
这些事实被加载到一个叫做"顶层"(top-level)的交互式提示符中。你通过在提示符中输入语句来查询;Prolog 尝试找到使该语句成立的所有方式。当有多个可能的解时,顶层会显示第一个解,然后等待用户输入。你可以让它再显示一个解、显示所有解,或者完全停止。
|
||||
|
||||
在第一个例子中,我们输入 `pokemon(squirtle).` 并回车。顶层回复 true。[Squirtle(杰尼龟)](https://bulbapedia.bulbagarden.net/wiki/Squirtle_(Pok%C3%A9mon))确实是一只宝可梦。
|
||||
|
||||
```
|
||||
?- pokemon(squirtle).
|
||||
true.
|
||||
```
|
||||
|
||||
如果你想在家跟着做,查看[这个仓库](https://github.com/alexpetros/prologdex),里面有快速设置说明。
|
||||
|
||||
不是所有东西都是宝可梦。
|
||||
|
||||
```
|
||||
?- pokemon(alex).
|
||||
false.
|
||||
```
|
||||
|
||||
让我们用谓词 type/2 来加入宝可梦属性。
|
||||
|
||||
```
|
||||
type(bulbasaur, grass).
|
||||
type(bulbasaur, poison).
|
||||
type(ivysaur, grass).
|
||||
type(ivysaur, poison).
|
||||
type(venusaur, grass).
|
||||
type(venusaur, poison).
|
||||
type(charmander, fire).
|
||||
type(charmeleon, fire).
|
||||
type(charizard, fire).
|
||||
type(charizard, flying).
|
||||
type(squirtle, water).
|
||||
type(wartortle, water).
|
||||
type(blastoise, water).
|
||||
```
|
||||
|
||||
实际数据集包含了宝可梦的不同形态,比如 Mega 进化,因为它们有不同的能力值和属性。这里为了清晰起见省略了这些。
|
||||
|
||||
回顾一下,有些宝可梦只有一种属性,而另有两种。对于后一种情况,用两个 type 事实来建模。[Bulbasaur(妙蛙种子)](https://bulbapedia.bulbagarden.net/wiki/Bulbasaur_(Pok%C3%A9mon))是草属性,同时 Bulbasaur 也是毒属性;两个都为真。这种范式类似于 SQL 数据库中的一对多关系。
|
||||
|
||||
交互式地,我们可以确认 Squirtle 是不是水属性。
|
||||
|
||||
```
|
||||
?- type(squirtle, water).
|
||||
true.
|
||||
```
|
||||
|
||||
我们能不能说 Squirtle 是草属性?
|
||||
|
||||
```
|
||||
?- type(squirtle, grass).
|
||||
false.
|
||||
```
|
||||
|
||||
不能,因为 Squirtle 是水属性。
|
||||
|
||||
假设我们不知道 Squirtle 是什么属性。我们可以问!
|
||||
|
||||
```
|
||||
?- type(squirtle, Type).
|
||||
Type = water.
|
||||
```
|
||||
|
||||
在 Prolog 中,以大写字母开头的名字是变量。Prolog 尝试将谓词与变量的所有可能匹配进行"合一"(unify)。不过这个特定谓词只有一种方式可以为真:Type 必须是 water,因为 Squirtle 的唯一属性就是水。
|
||||
|
||||
对于有两种属性的宝可梦,谓词会合一两次。
|
||||
|
||||
```
|
||||
?- type(venusaur, Type).
|
||||
Type = grass
|
||||
; Type = poison.
|
||||
```
|
||||
|
||||
语义上,第三行的分号表示"或"。当 Type = grass 或 Type = poison 时,type(venusaur, Type) 为真。
|
||||
|
||||
任何参数都可以是变量,这意味着我们可以从任意方向提问。所有草属性有哪些?只需让第一个参数是变量,第二个参数设为 grass。
|
||||
|
||||
```
|
||||
?- type(Pokemon, grass).
|
||||
Pokemon = bulbasaur
|
||||
; Pokemon = ivysaur
|
||||
; Pokemon = venusaur
|
||||
; Pokemon = oddish
|
||||
; Pokemon = gloom
|
||||
; Pokemon = vileplume
|
||||
; Pokemon = paras
|
||||
; Pokemon = parasect
|
||||
; Pokemon = bellsprout
|
||||
; ... .
|
||||
```
|
||||
|
||||
这个输出是我输入查询后,按了几次"n"得到前几个解,然后按回车退出的结果。我截断了,但提示符会愉快地列出全部 164 个。
|
||||
|
||||
逗号可以用来列出多个谓词——Prolog 会统一变量,使得所有谓词同时为真。列出所有水/冰属性只需问:哪些宝可梦同时与水和冰两种属性合一。
|
||||
|
||||
```
|
||||
?- type(Pokemon, water), type(Pokemon, ice).
|
||||
Pokemon = dewgong
|
||||
; Pokemon = cloyster
|
||||
; Pokemon = lapras
|
||||
; Pokemon = laprasgmax
|
||||
; Pokemon = spheal
|
||||
; Pokemon = sealeo
|
||||
; Pokemon = walrein
|
||||
; Pokemon = arctovish
|
||||
; Pokemon = ironbundle
|
||||
; false.
|
||||
```
|
||||
|
||||
不要被解以"或 false"结尾这件事困扰。这是搜索算法工作方式的结果;求解器寻找更多解,然后失败了。我承认,我不完全理解为什么它有时候这样有时候不这样,但这是预期的行为。
|
||||
|
||||
尽管 Pokemon 是一个变量,在查询的上下文中,它的两个实例必须是相同的(就像代数一样)。查询只对 Pokemon 的值进行合一,使得这两个谓词都成立。例如,水/冰属性的 [Dewgong(白海狮)](https://bulbapedia.bulbagarden.net/wiki/Dewgong_(Pok%C3%A9mon))是一个解,因为我们的程序包含以下两个事实:
|
||||
|
||||
```
|
||||
type(dewgong, water).
|
||||
type(dewgong, ice).
|
||||
```
|
||||
|
||||
因此,用 dewgong 替换 Pokemon 变量满足查询。相比之下,Squirtle 只是水属性:存在 pokemon(squirtle, water),但没有 pokemon(squirtle, ice)。查询要求两者都合一,所以 squirtle 不是 Pokemon 的可能取值。
|
||||
|
||||
宝可梦有很多数据可以玩。[Iron Bundle(铁包袱)](https://bulbapedia.bulbagarden.net/wiki/Iron_Bundle_(Pok%C3%A9mon))是一只很强力的水/冰属性宝可梦,特攻很高。到底有多高?
|
||||
|
||||
```
|
||||
?- pokemon_spa(ironbundle, SpA).
|
||||
SpA = 124.
|
||||
```
|
||||
|
||||
特攻这么高,我们想利用强力的特殊招式。Iron Bundle 会哪些特殊招式?
|
||||
|
||||
```
|
||||
?- learns(ironbundle, Move), move_category(Move, special).
|
||||
Move = aircutter
|
||||
; Move = blizzard
|
||||
; Move = chillingwater
|
||||
; Move = freezedry
|
||||
; Move = hydropump
|
||||
; Move = hyperbeam
|
||||
; Move = icebeam
|
||||
; Move = icywind
|
||||
; Move = powdersnow
|
||||
; Move = swift
|
||||
; Move = terablast
|
||||
; Move = waterpulse
|
||||
; Move = whirlpool.
|
||||
```
|
||||
|
||||
Freeze-Dry(冷冻干燥)是一个特别好的特殊招式。下面是一个查询,找出所有特攻大于 120、会 Freeze-Dry 的冰属性宝可梦。
|
||||
|
||||
```
|
||||
?- pokemon_spa(Pokemon, SpA), SpA #> 120, learns(Pokemon, freezedry), type(Pokemon, ice).
|
||||
Pokemon = glaceon, SpA = 130
|
||||
; Pokemon = kyurem, SpA = 130
|
||||
; Pokemon = kyuremwhite, SpA = 170
|
||||
; Pokemon = ironbundle, SpA = 124
|
||||
; false.
|
||||
```
|
||||
|
||||
在继续之前,最后一个概念:规则(Rules)。规则有头部(head)和主体(body),如果主体为真,则规则合一。
|
||||
|
||||
如果一个招式是物理招式或特殊招式,它就被认为是一个伤害招式。谓词 damaging_move/2 定义了所有造成直接伤害的招式。
|
||||
|
||||
```
|
||||
damaging_move(Move) :-
|
||||
move_category(Move, physical)
|
||||
; move_category(Move, special).
|
||||
```
|
||||
|
||||
这将与任何造成直接伤害的招式合一。
|
||||
|
||||
```
|
||||
?- damaging_move(tackle).
|
||||
true.
|
||||
?- damaging_move(rest).
|
||||
false.
|
||||
```
|
||||
|
||||
## SQL 对比
|
||||
|
||||
到目前为止,我展示的内容在逻辑上并不复杂——只是关于各种事实的"与"和"或"语句。它本质上是一个被美化的查找表。不过,请花一点时间来体会一下,查询这个数据库比常见的替代方案(比如 SQL)要舒服多少。
|
||||
|
||||
对于到目前为止我们看到的事实,我可能会这样设置 SQL 表:
|
||||
|
||||
```sql
|
||||
-- 省略其他能力值以保持简洁
|
||||
CREATE TABLE pokemon (pokemon_name TEXT, special_attack INTEGER);
|
||||
CREATE TABLE pokemon_types(pokemon_name TEXT, type TEXT);
|
||||
CREATE TABLE pokemon_moves(pokemon_name TEXT, move TEXT, category TEXT);
|
||||
```
|
||||
|
||||
把属性放在单独的表中而不是用 type_1 和 type_2 列,可能看起来有点刻意。这取决于具体用例,但由于哪个属性在前并不影响,你每次检查特定属性时都得查两个字段,我觉得这更容易出错。
|
||||
|
||||
然后像这样查询:
|
||||
|
||||
```sql
|
||||
SELECT DISTINCT pokemon, special_attack
|
||||
FROM pokemon as p
|
||||
WHERE
|
||||
p.special_attack > 120
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM pokemon_moves as pm
|
||||
WHERE p.pokemon_name = pm.pokemon_name AND move = 'freezedry'
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM pokemon_types as pt
|
||||
WHERE p.pokemon_name = pt.pokemon_name AND type = 'ice'
|
||||
);
|
||||
```
|
||||
|
||||
作为对比,再次给出等效的 Prolog 查询:
|
||||
|
||||
```
|
||||
?- pokemon_spa(Pokemon, SpA),
|
||||
SpA #> 120,
|
||||
learns(Pokemon, freezedry),
|
||||
type(Pokemon, ice).
|
||||
```
|
||||
|
||||
我不是在黑 SQL——我很喜欢 SQL——但这是大多数人接触过的最好的声明式查询语言了。Prolog 版本如此简单和灵活,真是令人惊叹。如果我们继续添加更多条件,SQL 查询会变得难以管理,而 Prolog 查询仍然易于阅读和编辑(一旦你掌握了变量的用法)。
|
||||
|
||||
## 升级
|
||||
|
||||
基础讲完了,现在说说我正在做的项目的背景。
|
||||
|
||||
宝可梦对战拥有数量惊人的机制,它们以复杂且概率性的方式相互影响。这些游戏的部分吸引力在于徒劳地试图比对手更好地记住所有机制,利用这些信息来预测和智取对方的计划。这有点像一种很傻的扑克。
|
||||
|
||||
我尚未提及的一小部分游戏机制:
|
||||
|
||||
- 某些招式有一定概率落空,不造成伤害。
|
||||
- 某些招式会提升或降低宝可梦的能力值。
|
||||
- 宝可梦可以携带具有各种效果的物品。
|
||||
- 伤害计算不是恒定的;招式的伤害在计算范围内呈正态分布。
|
||||
- 宝可梦可以被冰冻、灼伤、麻痹、中毒或陷入睡眠;这些都有各种负面效果。
|
||||
- 各种场地效果(如天气、场地、戏法空间)会改变招式伤害、出手顺序等。
|
||||
- 每只宝可梦都有一个特性,具有各种效果,例如:漂浮(Levitate)使你对地面招式免疫,降雨(Drizzle)在宝可梦上场时把天气变成下雨,强行(Sheer Force)禁用招式的副作用但将伤害乘以 1.3 倍。
|
||||
- 玩家在游戏前会(不可见地)为每只宝可梦分配努力值,以提升选定能力值。根据他们如何构建队伍,每只宝可梦可能比你预期的造成更多伤害或更能抗伤。
|
||||
|
||||
如果你想为这个游戏构建软件,挑战在于要在不疯掉的情况下对所有复杂性进行建模。Prolog 在这方面出奇地擅长,主要有两个原因:
|
||||
|
||||
看看这个[伤害计算器](https://calc.pokemonshowdown.com/),你就明白我的意思了。
|
||||
|
||||
- 查询模型擅长描述临时组合。
|
||||
- 数据模型非常适合以一致的方式分层叠加规则。
|
||||
|
||||
听众中的逻辑学家大概会想让我指出,查询模型和数据模型实际上是完全相同的。
|
||||
|
||||
为了说明这一点,这里是我如何为我的宝可梦选秀联赛实现优先度招式的。
|
||||
|
||||
宝可梦选秀大致就是字面意思。宝可梦根据实力被赋予一个分值,每个玩家获得一定数量的分数,你们轮流选直到每个玩家花完所有分数。你的队伍最终会有大约 8-11 只宝可梦,每周你和联赛中的另一个人正面交锋。几年前,我的朋友兼 [WMI](https://wemakeinter.net/) 合作者 [Morry](https://wttdotm.com/) 邀请我加入他的联赛,从那以后我就迷上了这个赛制。
|
||||
|
||||
比赛是 6v6,所以对战的一大部分是为你对手可能带来的所有六只组合做好准备,并组合出你自己的六只来应对所有情况。
|
||||
|
||||
当然,你只能用你选到的宝可梦来组建队伍。我只是用我的名字做了谓词:alex/1。
|
||||
|
||||
```
|
||||
alex(meowscarada).
|
||||
alex(weezinggalar).
|
||||
alex(swampertmega).
|
||||
alex(latios).
|
||||
alex(volcarona).
|
||||
alex(tornadus).
|
||||
alex(politoed).
|
||||
alex(archaludon).
|
||||
alex(beartic).
|
||||
alex(dusclops).
|
||||
```
|
||||
|
||||
我可不自豪选了一个雨天队。这大概是我开始以来最糟糕的选秀;这种事偶尔会发生。
|
||||
|
||||
我的宝可梦里有哪些会 Freeze-Dry?
|
||||
|
||||
```
|
||||
?- alex(Pokemon), learns(Pokemon, freezedry).
|
||||
false.
|
||||
```
|
||||
|
||||
一个都没有。唉。
|
||||
|
||||
非常重要的一类招式为优先度招式。之前我提到速度能力值决定哪只宝可梦先出手。这里有一些细微之处:使用最高优先度招式的宝可梦先出手,如果它们都选择了相同优先度的招式,那么速度更快的一方先出手。
|
||||
|
||||
大多数招式的优先度为零。
|
||||
|
||||
```
|
||||
?- move_priority(Move, P).
|
||||
Move = '10000000voltthunderbolt', P = 0
|
||||
; Move = absorb, P = 0
|
||||
; Move = accelerock, P = 1
|
||||
; Move = acid, P = 0
|
||||
; Move = acidarmor, P = 0
|
||||
; Move = aciddownpour, P = 0
|
||||
; Move = acidspray, P = 0
|
||||
; Move = acrobatics, P = 0
|
||||
; Move = acupressure, P = 0
|
||||
; Move = aerialace, P = 0
|
||||
; Move = aeroblast, P = 0
|
||||
```
|
||||
|
||||
啊,但不是全部!Accelerock(冲岩)的优先度为 1。使用 Accelerock 的宝可梦会先于任何使用优先度 0(或更低)招式的宝可梦行动,即使后者有更高的速度能力值。
|
||||
|
||||
我定义了一个 learns_priority/3 谓词,它合一于一只宝可梦、它学会的优先度招式以及该招式的优先度。
|
||||
|
||||
```
|
||||
learns_priority(Pokemon, Move, P) :-
|
||||
learns(Pokemon, Move),
|
||||
move_priority(Move, P),
|
||||
P #> 0.
|
||||
```
|
||||
|
||||
一个简单的查询——"我的队伍学会了哪些优先度招式"——返回了大量答案。
|
||||
|
||||
```
|
||||
?- alex(Pokemon), learns_priority(Pokemon, Move, Priority).
|
||||
Pokemon = meowscarada, Move = endure, Priority = 4
|
||||
; Pokemon = meowscarada, Move = helpinghand, Priority = 5
|
||||
; Pokemon = meowscarada, Move = protect, Priority = 4
|
||||
; Pokemon = meowscarada, Move = quickattack, Priority = 1
|
||||
; Pokemon = meowscarada, Move = allyswitch, Priority = 2
|
||||
; Pokemon = meowscarada, Move = suckerpunch, Priority = 1
|
||||
; Pokemon = weezinggalar, Move = endure, Priority = 4
|
||||
; Pokemon = weezinggalar, Move = protect, Priority = 4
|
||||
; Pokemon = swampertmega, Move = bide, Priority = 1
|
||||
; Pokemon = swampertmega, Move = endure, Priority = 4
|
||||
; Pokemon = swampertmega, Move = helpinghand, Priority = 5
|
||||
; Pokemon = swampertmega, Move = protect, Priority = 4
|
||||
; Pokemon = swampertmega, Move = wideguard, Priority = 3
|
||||
; Pokemon = latios, Move = allyswitch, Priority = 2
|
||||
; Pokemon = latios, Move = endure, Priority = 4
|
||||
; Pokemon = latios, Move = helpinghand, Priority = 5
|
||||
; Pokemon = latios, Move = magiccoat, Priority = 4
|
||||
; Pokemon = latios, Move = protect, Priority = 4
|
||||
; Pokemon = volcarona, Move = endure, Priority = 4
|
||||
; Pokemon = volcarona, Move = protect, Priority = 4
|
||||
; Pokemon = volcarona, Move = ragepowder, Priority = 2
|
||||
; Pokemon = tornadus, Move = endure, Priority = 4
|
||||
; Pokemon = tornadus, Move = protect, Priority = 4
|
||||
; Pokemon = politoed, Move = detect, Priority = 4
|
||||
; Pokemon = politoed, Move = endure, Priority = 4
|
||||
; Pokemon = politoed, Move = helpinghand, Priority = 5
|
||||
; Pokemon = politoed, Move = protect, Priority = 4
|
||||
; Pokemon = politoed, Move = bide, Priority = 1
|
||||
; Pokemon = archaludon, Move = endure, Priority = 4
|
||||
; Pokemon = archaludon, Move = protect, Priority = 4
|
||||
; Pokemon = beartic, Move = aquajet, Priority = 1
|
||||
; Pokemon = beartic, Move = bide, Priority = 1
|
||||
; Pokemon = beartic, Move = endure, Priority = 4
|
||||
; Pokemon = beartic, Move = protect, Priority = 4
|
||||
; Pokemon = dusclops, Move = allyswitch, Priority = 2
|
||||
; Pokemon = dusclops, Move = endure, Priority = 4
|
||||
; Pokemon = dusclops, Move = helpinghand, Priority = 5
|
||||
; Pokemon = dusclops, Move = protect, Priority = 4
|
||||
; Pokemon = dusclops, Move = shadowsneak, Priority = 1
|
||||
; Pokemon = dusclops, Move = snatch, Priority = 4
|
||||
; Pokemon = dusclops, Move = suckerpunch, Priority = 1
|
||||
; false.
|
||||
```
|
||||
|
||||
虽然这在技术上是对的(最好的一种对),但大多数答案实际上没什么用。帮助手(Helping Hand)和交换场地(Ally Switch)优先度很高,但它们只在双打对战中有用,而我不是在玩那个模式。
|
||||
|
||||
为了解决这个问题,我定义了所有双打招式并将它们排除。我也会排除招式变圆(Bide),它在功能上没用。\\+/1 谓词的意思是"如果这个目标失败则为真",而 dif/2 表示"这两个项不同"。
|
||||
|
||||
```
|
||||
learns_priority(Mon, Move, Priority) :-
|
||||
learns(Mon, Move),
|
||||
\+ doubles_move(Move),
|
||||
dif(Move, bide),
|
||||
move_priority(Move, Priority),
|
||||
Priority #> 0.
|
||||
|
||||
doubles_move(helpinghand).
|
||||
doubles_move(allyswitch).
|
||||
doubles_move(ragepowder).
|
||||
doubles_move(widesuard).
|
||||
doubles_move(snatch).
|
||||
doubles_move(magiccoat).
|
||||
```
|
||||
|
||||
搞定!这个查询只返回我的队伍相关的优先度招式。
|
||||
|
||||
## 组队
|
||||
|
||||
在实践中重要的事情是组建一个实际的六人队伍。队伍由你选到的宝可梦构成,好的队伍是对称的:如果你的一只宝可梦弱于某种属性,你队伍里的其他宝可梦应该能够应对那种属性。
|
||||
|
||||
你可以非常自然地用 Prolog 规则来编程这类约束,然后迭代它们,混合搭配组合,直到找到一个感觉对的方案。
|
||||
|
||||
我是这样定义队伍的:
|
||||
|
||||
```
|
||||
team_size([_,_,_,_,_,_]).
|
||||
```
|
||||
|
||||
这只与恰好有六个元素的列表合一。在谓词里数到六有点烦人,但我把它当作一个练习数数的机会。
|
||||
|
||||
好的队伍需要平衡。这是一个定义平衡的谓词:
|
||||
|
||||
```prolog
|
||||
balanced_team(Team) :-
|
||||
team_size(Team),
|
||||
member(Pokemon1, Team),
|
||||
member(Pokemon2, Team),
|
||||
Pokemon1 \= Pokemon2,
|
||||
type(Pokemon1, Type11),
|
||||
type(Pokemon2, Type21),
|
||||
weak_to(Type11, Type21).
|
||||
```
|
||||
|
||||
等等,这不对。实际上 Prolog 对什么为真什么为假非常严格,所以让我更仔细地想想……
|
||||
|
||||
```prolog
|
||||
balanced_team(Team) :-
|
||||
team_size(Team),
|
||||
% 对于队伍中的所有 Pokemon1
|
||||
findall(Type1, (member(P, Team), type(P, Type1)), Types1),
|
||||
% 检查 P1 的每个属性弱点是否都有某个 P2 抵抗它
|
||||
forall(
|
||||
(member(P1, Team), type(P1, T1), weak_to(T1, T2)),
|
||||
(member(P2, Team), P1 \= P2, type(P2, T2))
|
||||
).
|
||||
```
|
||||
|
||||
好吧,我不会假装我是 Prolog 专家——这些代码大部分第一次都没跑通——但 Prolog 的美妙之处在于,一旦你把事实搞对了,逻辑往往就水到渠成了。
|
||||
|
||||
## Prolog 的特别之处
|
||||
|
||||
我在 Python、JavaScript、SQL 和各种函数式语言上花了很多时间,Prolog 确实感觉与众不同。
|
||||
|
||||
我最大的领悟是:**Prolog 迫使你思考什么是真的,而不是做什么。** 在命令式编程中,你大部分精力花在控制流上——数据如何移动、以什么顺序执行。在 Prolog 中,你只需陈述什么是真的,然后让求解器来处理剩下的事情。
|
||||
|
||||
对于宝可梦对战——本质上是一个有着数千条互动规则的巨大规则引擎——这简直是天启。不用写 if/else 链来检查属性相克,你只需声明属性表,让 Prolog 处理其余部分。
|
||||
|
||||
例如,要找出你的哪只宝可梦能够对水/地面属性的 Swampert 造成效果绝佳的伤害:
|
||||
|
||||
```
|
||||
?- alex(P), learns(P, Move), move_type(Move, Type), super_effective(Type, water), super_effective(Type, ground).
|
||||
```
|
||||
|
||||
这一个查询就把你的队伍名单、招式池数据、招式属性和属性相克表组合成了一个简洁的语句。用 Python 试试看。
|
||||
|
||||
## 最后的思考
|
||||
|
||||
这个项目始于一个玩笑——"如果我拿 Prolog 来做宝可梦组队会怎样"——但它教给我的逻辑编程知识比任何教科书都多。
|
||||
|
||||
如果你感兴趣,我强烈推荐:
|
||||
1. 安装 SWI-Prolog 并玩玩 [prologdex](https://github.com/alexpetros/prologdex) 数据集
|
||||
2. 阅读 [Adventure in Prolog](http://www.amzi.com/AdventureInProlog/) 教程
|
||||
3. 尝试用 Prolog 对你熟悉的领域建模——它会彻底改变你对数据和关系的思考方式
|
||||
|
||||
祝你编码愉快,愿你的属性相克永远是效果绝佳。
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
> **译者注:**
|
||||
> 原文作者:Alexander Petros
|
||||
> 原文标题:Prolog Basics Explained with Pokémon
|
||||
> 原文链接:https://unplannedobsolescence.com/blog/prolog-basics-pokemon/
|
||||
> 本文为中文翻译转载。
|
||||
Reference in New Issue
Block a user