Open zgldh opened 2 years ago
再开发一下配套的程序和机器人,输入yaml就能做出菜来了
其实我觉得不需要设计新语言(这太难了),而可以在现有语言上开发一个库。例如用JavaScript描述做菜流程,每个原料
都是对象,锅
可以处理原料
。
其实我觉得不需要设计新语言(这太难了),而可以在现有语言上开发一个库。例如用JavaScript描述做菜流程,每个
原料
都是对象,锅
可以处理原料
。
最好是以配置的形式来做,比如 Dockerfile 这种。 如果是编程语言的库,那灵活性太大了,反而不可预知后果。
用code来表示recipe,然后code可以直接转换成人类可读的菜谱,或机器可读的自动炒菜程序。
我目前能看到的好处是:
前几天我在思考,如果有一种语言用来表示中餐食谱,它需要哪些基本语法。
然后我意识到,使用json、xml、yaml这类标记语言,很可能导致语法过度繁琐(他们都是设计用于处理很general的问题的)。这可能会打击贡献菜谱的厨师的积极性。这种语法最好写起来非常方便。
然后我考虑过python、js之类的现有脚本语言,语言的现有语法、灵活性似乎也会对编写者造成困扰。
我写了一个类似这样的sample,但最后想了想又感觉,相比于直接写markdown来说没有太大的优势。(毕竟现在没有家用做饭机器人)
我倾向于学习 Dockerfile 的做法。
From 引入别的菜谱 Material 定义原料 Kitchenware 定义炊具
Put 将原料放入炊具
Chopping 砧板工序(砧板) Steam 蒸炖工序(上什) Fry 烹炒工序(炉头) Paoding 屠宰分解(水台) Seasoning 料头预加工、装饰(打荷) Brine 烧蜡卤味工序(烧腊) Dimsum 点心面食工序(点心) 以上工序参考自这里
Export 将某炊具内容物导出为一个产物,产物可以被其他菜谱引用。也可以直接吃。如豆瓣酱、回锅肉都可以是产物。
抛砖引玉,有别的想法也说说。
是否可以将具体的做菜流程拆分成多个不同的步骤(Step),每个 Step 由一个或多个子 Step 或不可拆分的原子步骤(AtomStep)组成,通过定义触发器(Tigger)之类的组件应该可以描述整个流程。同时可以最大程度的保证每个步骤的清晰性,如 #55 的流程可以表示为:
Step1:
Input: 番茄
Handle:
Step1:
Input: 番茄
Output: $11
Handle: 开水烫番茄表皮
Step2:
Input: $11
Output: $12
Handle: $1 放入冷水
Step3:
Input: $12
Output: $13
Handle: 去掉 $12 外皮
Output: $13
Step2:
Input: $13
Output: $2
Handle: $13 切块
Step3:
Input: 鸡蛋,盐
Output: $3
Handle:
Step1:
Input: 鸡蛋
Output: $31
Handle: 鸡蛋打入碗中
Step2:
Input: $31
Output: $32
Handle: 加入 人数 * 1g 盐
Step3:
Input: $32
Output: $33
Handle: 搅拌均匀
Step4:
Input: 油 油数量为 鸡蛋数量 * 4 ml
Output: $4
Handle: 起锅烧油
Step5:
Input: $4
Trigger: $4 冒烟
Output: $5
Handle:
- 往 $4 中加入 $3
- 等待 10s 或两面金黄
// ……
单纯整活
已经有人做过了, 而且连APP都有了
Cooklang and the tools we’ve built to use it, you can:
simplify your personal recipe management; streamline your shopping routine; make cooking more fun. Here’s how the Cooklang ecosystem makes that happen:
All recipes are human-readable text files. Everything is a file. No databases. And you have complete control over your information. All the tools are simple, focused, and efficient; the UNIX way.
应该是recipeAsCode吧?
比如pipelineAsCode=gitops;recipeAsCode就是cookOps
Cooklang 没有本地化啊,我们需要中文编程。
Cooklang 没有本地化啊,我们需要中文编程。
可以的话甚至可以来点可视化(
Cooklang 没有本地化啊,我们需要中文编程。
菜谱怎么会跟自然语言强绑定呢?
>> 中文测试: 张三☺️
@西红柿{2个}洗净。去蒂,切成边长不超过 4cm 的小块
将@鸡蛋{2~3个}打入碗中,加入@盐{2%g},搅匀
热锅,加@油{8%ml}
(可选)加入@饮用水{300%ml},并加热 30 秒(这可以避免最终盛盘后可能的汤汁不足)
Cooklang 功能太弱了,只有食材提取的能力。 更关键的烹饪技法没有规范化。
还是开发一个食谱编辑器靠谱,参考 #135
https://www.dangermouse.net/esoteric/chef.html 这个语言的烹饪技法倒是规范化了,但是种类有点少,而且只有英语。 (其实这个语言根本不是 Recipe as Code 而是 Code as Recipe)
接入语音AI就可以当厨房助手了
分享一个做菜,你们也太卷了吧
分享一个做菜,你们也太卷了吧
这叫互联网思维啊 我觉得菜谱语言需要支持数量计算,不然得凭经验,对新手不友好 经验部分也能数字化
要不使用扩展 markdown 语法描述好了,还能转成思维导图
感觉食材的处理方式都可以被枚举吧……当成一个class来处理的可能性也是不小的 猪肉 pork1 = new 猪肉( 100g, 五花肉); pork1.切条(手指粗细); 锅 wok1 = new 锅(炒锅); wok1.add(油, 1茶勺); wok1.heat(100摄氏度); wok1.add(pork);
再格式化成 roff,就能 man 地三鲜
了(雾
做菜的过程可以抽象成一个整体串行,局部并行的流水线模型。食材和各种配料作为上下文 Context 在这个 Pipeline 中过一遍,最终产物即为烹饪好的菜品。从这个角度看 Github Actions 好像已经给了一个很好的模型了。
Cooklang 功能太弱了,只有食材提取的能力。 更关键的烹饪技法没有规范化。
其实我觉得Cooklang只能够作为定义中的食材提取的部分;而这个烹饪技法--如同大家所说--可以用github action或者gitlab-ci等方式来完成。
而烹饪技法的流水线,又可以进一步提高人类协作的效率,可能这是目前的唯一价值;不然厨师们为啥要用一个不熟悉的语言来定义菜谱呢?
当然最终目标还是输入菜谱之后,锅里面就能够标准化的生产出对应的菜,不过那个时候估计人工智能已经很发达了,还要喂养人类干嘛,当电池吗:)
toml更加易读
也是整活儿。。。
用顺序、条件、循环而不是静态配置表示做菜过程更自然。为了让菜谱更标准、可复制,可以把炊具、食材、操作者先做抽象,暴露出一系列API。比如微波炉实现 heater 接口,西红柿实现 cutable 和 isPeeled 接口,操作者可以是人或机器,实现类似 cutter 接口。基于这些抽象就可以写出标准的执行程序,具体使用哪种编程语言不关键。为了让菜谱更好理解,可以利用低代码方式比如可视化编程
感觉可以做一个通用的食谱编辑器,食谱提供者用 UI 界面录入食谱后程序自动转存为一个通用的格式(假定是数据库里),然后根据数据库里的数据可以自动生成各种格式的菜谱( Markdown , YAML 等 )。这样第三方的就程序可以自己读取转换后的产物或源数据。
可以考虑NLP+流程挖掘,使用BPMN或者Petri网表示菜谱,进而进行多模态转换
简单点可以直接用一个带时间约束和函数表示的AOE网,输入菜谱使用者的条件求得一个拓扑排序,再转为自然语言/进行代码生成
我有个新的想法,写一个脚本, 把食谱生成一个视频来,供大家边做边看,官网可以在菜谱旁边加一个链接,就是视频,当然最主要的是 语音表达好步骤,就行不用边做边看菜谱了。
是否可以将具体的做菜流程拆分成多个不同的步骤(Step),每个 Step 由一个或多个子 Step 或不可拆分的原子步骤(AtomStep)组成,通过定义触发器(Tigger)之类的组件应该可以描述整个流程。同时可以最大程度的保证每个步骤的清晰性,如 #55 的流程可以表示为:
Step1: Input: 番茄 Handle: Step1: Input: 番茄 Output: $11 Handle: 开水烫番茄表皮 Step2: Input: $11 Output: $12 Handle: $1 放入冷水 Step3: Input: $12 Output: $13 Handle: 去掉 $12 外皮 Output: $13 Step2: Input: $13 Output: $2 Handle: $13 切块 Step3: Input: 鸡蛋,盐 Output: $3 Handle: Step1: Input: 鸡蛋 Output: $31 Handle: 鸡蛋打入碗中 Step2: Input: $31 Output: $32 Handle: 加入 人数 * 1g 盐 Step3: Input: $32 Output: $33 Handle: 搅拌均匀 Step4: Input: 油 油数量为 鸡蛋数量 * 4 ml Output: $4 Handle: 起锅烧油 Step5: Input: $4 Trigger: $4 冒烟 Output: $5 Handle: - 往 $4 中加入 $3 - 等待 10s 或两面金黄 // ……
单纯整活
支持这个做法,把每一道菜所涉及的每一个步骤、工序、节点都进行量化,加上时间轴的纬度,有利于今后机器学习和炒菜机器人的实施。然后动态生成markdown文件,有利于人类的阅读。
是否可以将具体的做菜流程拆分成多个不同的步骤(Step),每个 Step 由一个或多个子 Step 或不可拆分的原子步骤(AtomStep)组成,通过定义触发器(Tigger)之类的组件应该可以描述整个流程。同时可以最大程度的保证每个步骤的清晰性,如 #55 的流程可以表示为:
Step1: Input: 番茄 Handle: Step1: Input: 番茄 Output: $11 Handle: 开水烫番茄表皮 Step2: Input: $11 Output: $12 Handle: $1 放入冷水 Step3: Input: $12 Output: $13 Handle: 去掉 $12 外皮 Output: $13 Step2: Input: $13 Output: $2 Handle: $13 切块 Step3: Input: 鸡蛋,盐 Output: $3 Handle: Step1: Input: 鸡蛋 Output: $31 Handle: 鸡蛋打入碗中 Step2: Input: $31 Output: $32 Handle: 加入 人数 * 1g 盐 Step3: Input: $32 Output: $33 Handle: 搅拌均匀 Step4: Input: 油 油数量为 鸡蛋数量 * 4 ml Output: $4 Handle: 起锅烧油 Step5: Input: $4 Trigger: $4 冒烟 Output: $5 Handle: - 往 $4 中加入 $3 - 等待 10s 或两面金黄 // ……
单纯整活
支持这个做法,把每一道菜所涉及的每一个步骤、工序、节点都进行量化,加上时间轴的纬度,有利于今后机器学习和炒菜机器人的实施。然后动态生成markdown文件,有利于人类的阅读。
如果用json会不会更好,yaml对于编写者的要求会更高,而且json中用一个大数组来维护步骤和原料是否对于机器更易读……?
这是修改后的 西红柿炒鸡蛋.md。
{
"head":{
"title":"西红柿炒鸡蛋的做法",
"description":"西红柿炒蛋是中国家常几乎最常见的一道菜肴。它的原材料易于搜集,制作步骤也较为简单,所以非常适合新厨师上手,是很多人学习做菜时做的第一道菜。"
},
"source":[//这里将“计算”和“必备原料和工具”合并在一起简短源码
{
"class":"source.tomato", //这里需要单独维护一个json来确保这个不会出现诸如“西红柿”和“番茄”的冲突,在进行构建pages的时候同时可以创建通向诸如“如何选购西红柿”的各种提示,方便后续需求,同时比如(约 180g)这种内容可以被定义在其中。
"optional":false,
"amount":{//这里指明每份的用量,
"type":"piece",//按照个计算,同时"gram"是以克计算
"count":1
},
"particular":[//特别指出的部分,可以对上面的内容复写,对于编译时展示的内容需要进行自行定义
{"type":"overwrite","class":"source.tomato","key":"weight","value":"180"}//覆写西红柿的重量
]
},
//...
{
"class":"source.green_onion",
"optional":true,
"amount":{
"type":"range",
"left":{
"type":"weight",
"counts":0
},
"right":{
"type":"weight",
"counts":10
},
},
"particular":[]
}
],
"step":[
{
"class":"source.tomato.wash",
"optional":false,
"particular":[]
},//可以说是最简短的步骤(
{
"class":"source.tomato.skin_remove",
"optional":true,
"particular":[]
},//同样的,“开水烫表皮,然后将西红柿放入冷水,剥去外皮”这句话被规范化后被定义在source.tomato.skin_remove里面
{
"class":"source.tomato.pedicle_remove",
"optional":false,
"particular":[]
},//步骤细分,有助于机器识别
{
"class":"operator.cut",
"optional":"false",
"operation":{
"object":"source.tomato",
"particular":[
{
"type":"overwrite",
"class":"operator.cut",
"key":"sized",
"value":true
},
{
"type":"append",
"class":"operator.cut",
"key":"to_volume",
"value":"64"
},
{
"type":"overwrite",
"class":"operator.cut",
"key":"__volume_into_ridge_length",
"value":true
}
]
},
"particular":[
{
"type":"macro",//宏
"class":".",//.指代本文件
"key":"","value":"",//宏对于固定键不兼容
"macro":"DEF '西红柿块' AS {.}.result"//{.}指代"particular"所在的步骤,result是抽象的产物
}
]
},
//...
{
"type":"operator.plate",
"optional":false,
"particular":[]
}
]
}
当然缺点在我改完这一点之后我感觉比较明显了……
文件大小最起码要增大一倍,对于原料的定义需要更多更多的文件,编译生成Markdown的时候需要更多更多的中间代码……好处可能是厨师机器人可以抽象的多线程(虽然会有堵塞和死锁)的做出菜……
github的markdown展示中json不支持注释...虽然说本来JSON规范也不推荐加入注释,将就看一下吧……orz
可能整了个坏活
2021年谷歌在人机交互顶会UIST发了一篇论文可以把markdown的菜谱生成教学视频,分享在这里供大家参考: 论文链接:https://dl.acm.org/doi/10.1145/3472749.3474778 视频:https://www.youtube.com/watch?v=oq4RuqEb6Eg&list=PLqhXYFYmZ-VeKUIuttbQWomTQ-oXF6PLf&index=83
可以先编写几个基本类工具
,食材
等等,然后派生成为更详细的内容。
如番茄
,锅
,油
这些,然后抽象出方法(对于锅来说,有炒
,对于番茄来说有去皮
,切丁
等)这样在不同的解释器里可以针对相同的方法给出不同的动作。
如对于电脑,则直接显示在屏幕上,对于语音控制设备,则发出语音指示,对于(以后可能出现的)做饭机器人,则执行动作等等。
我的想法是基于现有的语言开发多套解析语法相同,但是解析结果不同的库来适配这些情况,这样,每个文件描述的是做饭的流程,也可以使用原生的import之类的功能避免重复coding。
这是我的想法最后的文件描述(我也不知道描述了什么,但是思想应该传递到了):
import { Tomato, Water } from "Cooklib/Ingredients";
import { Knife, Pot } from "Cooklib/Tools";
function MakeDish(peopleNum) {
Water.require("size", peopleNum * 1500, peopleNum * 2000)//1500至2000ml水
.then(() => {
Tomato.require("size", 100, 500)//要求每个在100克到500克之间,此时做饭人或机器可以自检,通过后继续执行
.then(() => {
Tomato.require("num", peopleNum * 3, peopleNum * 5)//要求3到5个
.then(() => {
Knife.cutInPieces(Tomato)
.then((TomatoPiece) => {
Pot.Put(Water, Water.size)//将 水 放入 锅 (其实倒入会更好)
.then(() => {
Pot.BoilUntilTemperature(Water.boilingPoint)//将水烧到沸点(沸点可以根据当地气压测量)
.then(() => {
Pot.put(TomatoPiece, 1.0)//放入100%数量的番茄碎
.then(() => {
Pot.BoilUntilTime(60 * 1000)//等待60000毫秒(一分钟)
.then(() => {
return { "status": 0, "element": Pot.getElement() };//将锅中物品装盘备用(doge)
})
})
})
})
});
})
});
})
.catch((err) => {
return { "status": -1, "err": err.code };
})
}
如果有什么更好的想法欢迎提出来。
可以先编写几个基本类
工具
,食材
等等,然后派生成为更详细的内容。 如番茄
,锅
,油
这些,然后抽象出方法(对于锅来说,有炒
,对于番茄来说有去皮
,切丁
等)这样在不同的解释器里可以针对相同的方法给出不同的动作。 如对于电脑,则直接显示在屏幕上,对于语音控制设备,则发出语音指示,对于(以后可能出现的)做饭机器人,则执行动作等等。 我的想法是基于现有的语言开发多套解析语法相同,但是解析结果不同的库来适配这些情况,这样,每个文件描述的是做饭的流程,也可以使用原生的import之类的功能避免重复coding。 这是我的想法最后的文件描述(我也不知道描述了什么,但是思想应该传递到了):import { Tomato, Water } from "Cooklib/Ingredients"; import { Knife, Pot } from "Cooklib/Tools"; function MakeDish(peopleNum) { Water.require("size", peopleNum * 1500, peopleNum * 2000)//1500至2000ml水 .then(() => { Tomato.require("size", 100, 500)//要求每个在100克到500克之间,此时做饭人或机器可以自检,通过后继续执行 .then(() => { Tomato.require("num", peopleNum * 3, peopleNum * 5)//要求3到5个 .then(() => { Knife.cutInPieces(Tomato) .then((TomatoPiece) => { Pot.Put(Water, Water.size)//将 水 放入 锅 (其实倒入会更好) .then(() => { Pot.BoilUntilTemperature(Water.boilingPoint)//将水烧到沸点(沸点可以根据当地气压测量) .then(() => { Pot.put(TomatoPiece, 1.0)//放入100%数量的番茄碎 .then(() => { Pot.BoilUntilTime(60 * 1000)//等待60000毫秒(一分钟) .then(() => { return { "status": 0, "element": Pot.getElement() };//将锅中物品装盘备用(doge) }) }) }) }) }); }) }); }) .catch((err) => { return { "status": -1, "err": err.code }; }) }
如果有什么更好的想法欢迎提出来。
可怕的回调地狱,async / await 语法多好
import { Tomato, Water } from 'Cooklib/Ingredients'
import { Knife, Pot } from 'Cooklib/Tools'
async function MakeDish(peopleNum) {
try {
await Water.require('size', peopleNum * 1500, peopleNum * 2000) //1500至2000ml水
await Tomato.require('size', 100, 500) //要求每个在100克到500克之间,此时做饭人或机器可以自检,通过后继续执行
await Tomato.require('num', peopleNum * 3, peopleNum * 5) //要求3到5个
const TomatoPiece = await Knife.cutInPieces(Tomato)
await Pot.Put(Water, Water.size) //将 水 放入 锅 (其实倒入会更好)
await Pot.BoilUntilTemperature(Water.boilingPoint) //将水烧到沸点(沸点可以根据当地气压测量)
await Pot.put(TomatoPiece, 1.0) //放入100%数量的番茄碎
await Pot.BoilUntilTime(60 * 1000) //等待60000毫秒(一分钟)
return { status: 0, element: Pot.getElement() } //将锅中物品装盘备用(doge)
} catch (error) {
return { status: -1, err: err.code }
}
}
学习一下makefile?
是否可以将具体的做菜流程拆分成多个不同的步骤(Step),每个 Step 由一个或多个子 Step 或不可拆分的原子步骤(AtomStep)组成,通过定义触发器(Tigger)之类的组件应该可以描述整个流程。同时可以最大程度的保证每个步骤的清晰性,如 #55 的流程可以表示为:
Step1: Input: 番茄 Handle: Step1: Input: 番茄 Output: $11 Handle: 开水烫番茄表皮 Step2: Input: $11 Output: $12 Handle: $1 放入冷水 Step3: Input: $12 Output: $13 Handle: 去掉 $12 外皮 Output: $13 Step2: Input: $13 Output: $2 Handle: $13 切块 Step3: Input: 鸡蛋,盐 Output: $3 Handle: Step1: Input: 鸡蛋 Output: $31 Handle: 鸡蛋打入碗中 Step2: Input: $31 Output: $32 Handle: 加入 人数 * 1g 盐 Step3: Input: $32 Output: $33 Handle: 搅拌均匀 Step4: Input: 油 油数量为 鸡蛋数量 * 4 ml Output: $4 Handle: 起锅烧油 Step5: Input: $4 Trigger: $4 冒烟 Output: $5 Handle: - 往 $4 中加入 $3 - 等待 10s 或两面金黄 // ……
单纯整活
認同,但我覺得有些地方可能會讓食譜編寫及理解的難度提升:
StepN
的編號看起來有點冗長,我覺得可以改為 YAML 的列表語法。Step1
的子步驟基本上就是將前一步驟的產物(output)作為下一步驟的原料(input),也就是 pipeline 的概念,可以簡化。冗長的 Input
與 Output
可能導致食譜編寫上容易出錯。我參考 GitHub Actions,設計了較接近自然語言風格的語法,且可以對應到現有的 Markdown 描述方式。
示例(根據 現有的「西红柿炒鸡蛋」食譜,但簡化過,例如所有食材都需要洗淨就省略了):
name: 西红柿炒鸡蛋
desc: 西红柿炒蛋是中国家常几乎最常见的一道菜肴。它的原材料易于搜集,制作步骤也较为简单,所以非常适合新厨师上手,是很多人学习做菜时做的第一道菜。
ingredients:
- item: 番茄
amount: 1
unit: 顆
- item: 雞蛋
amount: 1.5
unit: 顆
- item: 食用油
amount: 6
unit: mL
- item: 鹽
amount: 1.5-2
unit: g
- item: 醋
amount: 0|1
unit: mL
- item: 糖
amount: 0-2
unit: g
- item: 蔥花
amount: 0-10
unit: g
jobs:
- dependencies:
- 番茄
steps:
- optional: true
desc: 去掉番茄的外表皮
do:
- 用開水燙其表皮
- 放入冷水
- 剝去外皮
- do: 去蒂
- do: 切成邊長不超過 4 cm 的塊
artifact: 番茄塊
- dependencies:
- 雞蛋
- 鹽
- 醋
steps:
- do: 將雞蛋打入碗中
- do: 加入 (1 * 份數) g 的鹽
- do: 加入醋
- do: 攪勻
artifact: 雞蛋液
- dependencies:
- 食用油
- 雞蛋液
- 番茄塊
steps:
- do: 熱鍋
- do: 加入食用油
- do: 等待
until: 油熱
- do: 加入雞蛋液
- do: 翻炒
until: 雞蛋結為固體且顏色微黃
- do: 關火
- do: 將雞蛋盛盤
- do: 開火
- do: 加入番茄塊
- do: 用鍋鏟拍打並翻炒
for:
amount: 20
unit: s
until: 番茄變得軟爛
- do: 加入剛剛盛出的雞蛋
- do: 翻炒均勻
- do: 加入剩餘的鹽
- do: 加入糖
- do: 加入蔥花
- do: 翻炒均勻
- do: 結束
說明:
name
:菜品的名稱。desc
:菜品的描述。ingredients
:一份(通常正好夠一人食用)所需的食材,可按比例調整。jobs
:要做的事,每個 job 都有各自的產物,且每個 job 都可並行(concurrent),除非它們之間有依賴關係,例如可以同時切番茄和打蛋而不互相衝突。jobs[*].dependencies
:job 中涉及的食材(可能是其他 job 加工過的)。jobs[*].artifact
:job 輸出的產物,只能是字串,其表示一個整體或集合,例如「番茄塊」不僅指一個番茄塊。jobs[*].steps
:job 的步驟,是一連串對一個整體物品、集合或容器進行的操作集合。jobs[*].steps[*].desc
:步驟的整體描述,如果沒有子步驟不建議使用。jobs[*].steps[*].do
:步驟的動作,可以是一個列表來表示子步驟,應儘可能簡短。jobs[*].steps[*].until
:停止動作的條件。jobs[*].steps[*].for
:動作持續的時間。不過還有許多問題有待改進,像盛出半熟雞蛋的地方,應該要切成兩個 job 的,但是那樣似乎無法表示先後順序。
PS:抱歉用的是繁體,希望不會造成溝通上的困難。
使用规范定义的标记语言、JSON 或 yaml 来定义每一个菜谱。 有利于使用程序进行深加工,挖掘菜谱更多价值。