IT牛人博客聚合网站 发现IT技术最优秀的内容, 寻找IT技术的价值 http://www.udpwork.com/ zh_CN http://www.udpwork.com/about hourly 1 Thu, 23 Feb 2017 04:07:26 +0800 <![CDATA[快十年了,我们再聊聊]]> http://www.udpwork.com/item/16144.html http://www.udpwork.com/item/16144.html#reviews Wed, 22 Feb 2017 18:29:43 +0800 RobinDong http://www.udpwork.com/item/16144.html Dong:http://oldblog.donghao.org/2007/09/euoo.html
你说时间过得多快

Hero: ...那时怎么大家这么无聊
无聊得来有基情
http://oldblog.donghao.org/2008/02/io.html唐唐是谁?

Dong: 唐勇,2008年我挪到他下面,就没有跟中中在杭州一起加班了
“要求英俊潇洒、玉树临风、还要懂c++、懂后台系统”,现在公司里满足这个要求的就多多了,
还是钱多雇得起啊

Hero: 哦哦

Dong:http://oldblog.donghao.org/2007/09/u.html
 这个才是境界

Hero: ...真无聊
关于我的有这么多?还有呢

Dong: 无聊的另一个意思就是:禅
。你计划书上要注意用词

Hero: 有道理,英文是zen

Dong:http://oldblog.donghao.org/2007/09/euoaee.html
主要是你这位大师在制造zen

Hero: ...

Dong: 不过最近浏览论文才发现,CNN居然是多伦多大学的一位教授最早提出的,
此人现在还在从事深度学习方面的研究,论文也还在发。说真的,你顺路可能去拜访他吗?

Hero: 是啊,很棒的啊,人被 google 收购了
拜访他干嘛啊

Dong: 问得好,看来我得研究明白了,然后发两篇有价值的论文,然后让公司赞助我去多伦多开个学术会,
然后我就能当面请教和沟通了,比单纯的拜访有价值

Hero: 就是
拜访没意义,禅又对不上

Dong: zen这个东西,我们几个老熟人闲的蛋疼的时候搞搞就好了

]]>
Dong:http://oldblog.donghao.org/2007/09/euoo.html
你说时间过得多快

Hero: ...那时怎么大家这么无聊
无聊得来有基情
http://oldblog.donghao.org/2008/02/io.html唐唐是谁?

Dong: 唐勇,2008年我挪到他下面,就没有跟中中在杭州一起加班了
“要求英俊潇洒、玉树临风、还要懂c++、懂后台系统”,现在公司里满足这个要求的就多多了,
还是钱多雇得起啊

Hero: 哦哦

Dong:http://oldblog.donghao.org/2007/09/u.html
 这个才是境界

Hero: ...真无聊
关于我的有这么多?还有呢

Dong: 无聊的另一个意思就是:禅
。你计划书上要注意用词

Hero: 有道理,英文是zen

Dong:http://oldblog.donghao.org/2007/09/euoaee.html
主要是你这位大师在制造zen

Hero: ...

Dong: 不过最近浏览论文才发现,CNN居然是多伦多大学的一位教授最早提出的,
此人现在还在从事深度学习方面的研究,论文也还在发。说真的,你顺路可能去拜访他吗?

Hero: 是啊,很棒的啊,人被 google 收购了
拜访他干嘛啊

Dong: 问得好,看来我得研究明白了,然后发两篇有价值的论文,然后让公司赞助我去多伦多开个学术会,
然后我就能当面请教和沟通了,比单纯的拜访有价值

Hero: 就是
拜访没意义,禅又对不上

Dong: zen这个东西,我们几个老熟人闲的蛋疼的时候搞搞就好了

]]>
0
<![CDATA[编写地道的Go代码]]> http://www.udpwork.com/item/16139.html http://www.udpwork.com/item/16139.html#reviews Wed, 22 Feb 2017 17:41:21 +0800 鸟窝 http://www.udpwork.com/item/16139.html 在阅读本文之前,我先推荐你阅读官方的Effective Go文档,或者是中文翻译版:高效Go编程,它提供了很多编写标准而高效的Go代码指导,本文不会再重复介绍这些内容。

最地道的Go代码就是Go的标准库的代码,你有空的时候可以多看看Google的工程师是如何实现的。

本文仅作为一个参考,如果你有好的建议和意见,欢迎添加评论。

注释

可以通过/* …… */或者// ……增加注释,//之后应该加一个空格。

如果你想在每个文件中的头部加上注释,需要在版权注释和 Package前面加一个空行,否则版权注释会作为Package的注释。

12345678910111213
// Copyright 2009 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file./*Package net provides a portable interface for network I/O, includingTCP/IP, UDP, domain name resolution, and Unix domain sockets.......*/package net......

注释应该用一个完整的句子,注释的第一个单词应该是要注释的指示符,以便在godoc中容易查找。

注释应该以一个句点.结束。

声明slice

声明空的slice应该使用下面的格式:

1
var t []string

而不是这种格式:

1
t := []string{}

前者声明了一个nilslice而后者是一个长度为0的非nil的slice。

关于字符串大小写

错误字符串不应该大写。
应该写成:

1
fmt.Errorf("failed to write data")

而不是写成:

1
fmt.Errorf("Failed to write data")

这是因为这些字符串可能和其它字符串相连接,组合后的字符串如果中间有大写字母开头的单词很突兀,除非这些首字母大写单词是固定使用的单词。

缩写词必须保持一致,比如都大写URL或者小写url。比如HTTP、ID等。
例如sendOAuth或者oauthSend。

常量一般声明为MaxLength,而不是以下划线分隔MAX_LENGTH或者MAXLENGTH。

也就是Go语言一般使用MixedCaps或者mixedCaps命名的方式区分包含多个单词的名称。

处理error而不是panic或者忽略

为了编写强壮的代码,不用使用_忽略错误,而是要处理每一个错误,尽管代码写起来可能有些繁琐。

尽量不要使用panic。

尽量减少代码缩进

一些名称

有些单词可能有多种写法,在项目中应该保持一致,比如Golang采用的写法:

1234
// marshaling// unmarshaling// canceling// cancelation

而不是

1234
// marshalling// unmarshalling// cancelling// cancellation

包名应该用单数的形式,比如util、model,而不是utils、models。

Receiver 的名称应该缩写,一般使用一个或者两个字符作为Receiver的名称,如

123
func (f foo) method() {	...}

如果方法中没有使用receiver,还可以省略receiver name,这样更清晰的表明方法中没有使用它:

123
func (foo) method() {	...}

package级的Error变量

通常会把自定义的Error放在package级别中,统一进行维护:

123456789
var (	ErrCacheMiss = errors.New("memcache: cache miss")	ErrCASConflict = errors.New("memcache: compare-and-swap conflict")	ErrNotStored = errors.New("memcache: item not stored")	ErrServerError = errors.New("memcache: server error")	ErrNoStats = errors.New("memcache: no statistics available")	ErrMalformedKey = errors.New("malformed: key is too long or contains invalid characters")	ErrNoServers = errors.New("memcache: no servers configured or available"))

并且变量以Err开头。

空字符串检查

不要使用下面的方式检查空字符串:

123
if len(s) == 0 {	...}

而是使用下面的方式

123
if s == "" {	...}

下面的方法更是语法不对:

123
if s == nil || s == "" {	...}

非空slice检查

不要使用下面的方式检查空的slice:

123
if s != nil && len(s) > 0 {    ...}

直接比较长度即可:

123
if len(s) > 0 {    ...}

同样的道理也适用map和channel。

省略不必要的变量

比如

1
var whitespaceRegex, _ = regexp.Compile("\\s+")

可以简写为

1
var whitespaceRegex = regexp.MustCompile(`\s+`)

有时候你看到的一些第三方的类提供了类似的方法:

12
func Foo(...) (...,error)func MustFoo(...) (...)

MustFoo一般提供了一个不带error返回的类型。

直接使用bool值

对于bool类型的变量var b bool,直接使用它作为判断条件,而不是使用它和true/false进行比较

12345678
if b {    ...}if !b {    ...}

而不是

1234567
if b == true {    ...}if b == false {    ...}

byte/string slice相等性比较

不要使用

12345
   var s1 []byte   var s2 []byte   ...bytes.Compare(s1, s2) == 0bytes.Compare(s1, s2) != 0

而是:

12345
   var s1 []byte   var s2 []byte   ...bytes.Equal(s1, s2) == 0bytes.Equal(s1, s2) != 0

检查是否包含子字符串

不要使用strings.IndexRune(s1, 'x') > -1及其类似的方法IndexAny、Index检查字符串包含,
而是使用strings.ContainsRune、strings.ContainsAny、strings.Contains来检查。

使用类型转换而不是struct字面值

对于两个类型:

123456789
type t1 struct {	a int	b int}type t2 struct {	a int	b int}

可以使用类型转换将类型t1的变量转换成类型t2的变量,而不是像下面的代码进行转换

12
v1 := t1{1, 2}_ = t2{v1.a, v1.b}

应该使用类型转换,因为这两个struct底层的数据结构是一致的。

1
_ = t2(v1)

复制slice

不要使用下面的复制slice的方式:

12345678
var b1, b2 []byte	for i, v := range b1 { 		b2[i] = v	}	for i := range b1 { 		b2[i] = b1[i]	}

而是使用内建的copy函数:

1
copy(b2, b1)

不要在for中使用多此一举的true

不要这样:

12
for true {}

而是要这样:

12
for {}

尽量缩短if

下面的代码:

12345
   x := trueif x {	return true}return false

可以用return x代替。

同样下面的代码也可以使用return err代替:

12345678
func fn1() error {	var err error	if err != nil {		return err	}	return nil}
1234567891011
func fn1() bool{    ...    b := fn()	if b {		...         return true	} else {        return false    }}

应该写成:

123456789101112
func fn1() bool{    ...    b := fn()    if !b {        return false    }		...     return true	}

也就是减少if的分支/缩进。

###
不要这样:

1234
   var a, b []intfor _, v := range a {	b = append(b, v)}

而是要这样

12
var a, b []intb = append(b, a...)

简化range

123456
   var m map[string]int   for _ = range m { }for _, _ = range m {}

可以简化为

123
for range m {}

对slice和channel也适用。

正则表达式中使用raw字符串避免转义字符

在使用正则表达式时,不要:

12
regexp.MustCompile("\\.") regexp.Compile("\\.")

而是直接使用raw字符串,可以避免大量的\出现:

12
regexp.MustCompile(`\.`) regexp.Compile(`\.`)

简化只包含单个case的select

123
select {	case <-ch:}

直接写成<-ch即可。send也一样。

123456
   for { 	select {	case x := <-ch:		_ = x	}}

直接改成for-range即可。

这种简化只适用包含单个case的情况。

slice的索引

有时可以忽略slice的第一个索引或者第二个索引:

123
var s []int_ = s[:len(s)]_ = s[0:len(s)]

可以写成s[:]

使用time.Since

下面的代码经常会用到:

1
_ = time.Now().Sub(t1)

可以简写为:

1
_ = time.Since(t1)

使用strings.TrimPrefix/strings.TrimSuffix 掐头去尾

不要自己判断字符串是否以XXX开头或者结尾,然后自己再去掉XXX,而是使用现成的strings.TrimPrefix/strings.TrimSuffix。

123456
   var s1 = "a string value"   var s2 = "a "   var s3 stringif strings.HasPrefix(s1, s2) { 	s3 = s1[len(s2):]}

可以简化为

123
var s1 = "a string value"var s2 = "a "var s3 = strings.TrimPrefix(s1, s2)

使用工具检查你的代码

以上的很多优化规则都可以通过工具检查,下面列出了一些有用的工具:

  1. go fmt
  2. go vet
  3. gosimple
  4. keyify
  5. staticcheck
  6. unused
  7. golint
  8. misspell
  9. goimports
  10. errcheck

参考文档

  1. https://golang.org/doc/effective_go.html
  2. https://github.com/golang/go/wiki/CodeReviewComments
  3. https://dmitri.shuralyov.com/idiomatic-go
  4. https://talks.golang.org/2014/readability.slide#1
  5. https://github.com/dominikh/go-tools/tree/master/simple
  6. https://github.com/dominikh/go-tools/tree/master/cmd/structlayout-optimize
  7. https://go-zh.org
  8. https://docs.google.com/presentation/d/1OT-dMNbiwOPeaivQOldok2hUUNMTvSC0GJ67JohLt5U/pub?start=false&loop=false&delayms=3000&slide=id.g18b1f95882_1_135
  9. https://github.com/d-smith/go-training/blob/master/idiomatic-go.md
]]>
在阅读本文之前,我先推荐你阅读官方的Effective Go文档,或者是中文翻译版:高效Go编程,它提供了很多编写标准而高效的Go代码指导,本文不会再重复介绍这些内容。

最地道的Go代码就是Go的标准库的代码,你有空的时候可以多看看Google的工程师是如何实现的。

本文仅作为一个参考,如果你有好的建议和意见,欢迎添加评论。

注释

可以通过/* …… */或者// ……增加注释,//之后应该加一个空格。

如果你想在每个文件中的头部加上注释,需要在版权注释和 Package前面加一个空行,否则版权注释会作为Package的注释。

12345678910111213
// Copyright 2009 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file./*Package net provides a portable interface for network I/O, includingTCP/IP, UDP, domain name resolution, and Unix domain sockets.......*/package net......

注释应该用一个完整的句子,注释的第一个单词应该是要注释的指示符,以便在godoc中容易查找。

注释应该以一个句点.结束。

声明slice

声明空的slice应该使用下面的格式:

1
var t []string

而不是这种格式:

1
t := []string{}

前者声明了一个nilslice而后者是一个长度为0的非nil的slice。

关于字符串大小写

错误字符串不应该大写。
应该写成:

1
fmt.Errorf("failed to write data")

而不是写成:

1
fmt.Errorf("Failed to write data")

这是因为这些字符串可能和其它字符串相连接,组合后的字符串如果中间有大写字母开头的单词很突兀,除非这些首字母大写单词是固定使用的单词。

缩写词必须保持一致,比如都大写URL或者小写url。比如HTTP、ID等。
例如sendOAuth或者oauthSend。

常量一般声明为MaxLength,而不是以下划线分隔MAX_LENGTH或者MAXLENGTH。

也就是Go语言一般使用MixedCaps或者mixedCaps命名的方式区分包含多个单词的名称。

处理error而不是panic或者忽略

为了编写强壮的代码,不用使用_忽略错误,而是要处理每一个错误,尽管代码写起来可能有些繁琐。

尽量不要使用panic。

尽量减少代码缩进

一些名称

有些单词可能有多种写法,在项目中应该保持一致,比如Golang采用的写法:

1234
// marshaling// unmarshaling// canceling// cancelation

而不是

1234
// marshalling// unmarshalling// cancelling// cancellation

包名应该用单数的形式,比如util、model,而不是utils、models。

Receiver 的名称应该缩写,一般使用一个或者两个字符作为Receiver的名称,如

123
func (f foo) method() {	...}

如果方法中没有使用receiver,还可以省略receiver name,这样更清晰的表明方法中没有使用它:

123
func (foo) method() {	...}

package级的Error变量

通常会把自定义的Error放在package级别中,统一进行维护:

123456789
var (	ErrCacheMiss = errors.New("memcache: cache miss")	ErrCASConflict = errors.New("memcache: compare-and-swap conflict")	ErrNotStored = errors.New("memcache: item not stored")	ErrServerError = errors.New("memcache: server error")	ErrNoStats = errors.New("memcache: no statistics available")	ErrMalformedKey = errors.New("malformed: key is too long or contains invalid characters")	ErrNoServers = errors.New("memcache: no servers configured or available"))

并且变量以Err开头。

空字符串检查

不要使用下面的方式检查空字符串:

123
if len(s) == 0 {	...}

而是使用下面的方式

123
if s == "" {	...}

下面的方法更是语法不对:

123
if s == nil || s == "" {	...}

非空slice检查

不要使用下面的方式检查空的slice:

123
if s != nil && len(s) > 0 {    ...}

直接比较长度即可:

123
if len(s) > 0 {    ...}

同样的道理也适用map和channel。

省略不必要的变量

比如

1
var whitespaceRegex, _ = regexp.Compile("\\s+")

可以简写为

1
var whitespaceRegex = regexp.MustCompile(`\s+`)

有时候你看到的一些第三方的类提供了类似的方法:

12
func Foo(...) (...,error)func MustFoo(...) (...)

MustFoo一般提供了一个不带error返回的类型。

直接使用bool值

对于bool类型的变量var b bool,直接使用它作为判断条件,而不是使用它和true/false进行比较

12345678
if b {    ...}if !b {    ...}

而不是

1234567
if b == true {    ...}if b == false {    ...}

byte/string slice相等性比较

不要使用

12345
   var s1 []byte   var s2 []byte   ...bytes.Compare(s1, s2) == 0bytes.Compare(s1, s2) != 0

而是:

12345
   var s1 []byte   var s2 []byte   ...bytes.Equal(s1, s2) == 0bytes.Equal(s1, s2) != 0

检查是否包含子字符串

不要使用strings.IndexRune(s1, 'x') > -1及其类似的方法IndexAny、Index检查字符串包含,
而是使用strings.ContainsRune、strings.ContainsAny、strings.Contains来检查。

使用类型转换而不是struct字面值

对于两个类型:

123456789
type t1 struct {	a int	b int}type t2 struct {	a int	b int}

可以使用类型转换将类型t1的变量转换成类型t2的变量,而不是像下面的代码进行转换

12
v1 := t1{1, 2}_ = t2{v1.a, v1.b}

应该使用类型转换,因为这两个struct底层的数据结构是一致的。

1
_ = t2(v1)

复制slice

不要使用下面的复制slice的方式:

12345678
var b1, b2 []byte	for i, v := range b1 { 		b2[i] = v	}	for i := range b1 { 		b2[i] = b1[i]	}

而是使用内建的copy函数:

1
copy(b2, b1)

不要在for中使用多此一举的true

不要这样:

12
for true {}

而是要这样:

12
for {}

尽量缩短if

下面的代码:

12345
   x := trueif x {	return true}return false

可以用return x代替。

同样下面的代码也可以使用return err代替:

12345678
func fn1() error {	var err error	if err != nil {		return err	}	return nil}
1234567891011
func fn1() bool{    ...    b := fn()	if b {		...         return true	} else {        return false    }}

应该写成:

123456789101112
func fn1() bool{    ...    b := fn()    if !b {        return false    }		...     return true	}

也就是减少if的分支/缩进。

###
不要这样:

1234
   var a, b []intfor _, v := range a {	b = append(b, v)}

而是要这样

12
var a, b []intb = append(b, a...)

简化range

123456
   var m map[string]int   for _ = range m { }for _, _ = range m {}

可以简化为

123
for range m {}

对slice和channel也适用。

正则表达式中使用raw字符串避免转义字符

在使用正则表达式时,不要:

12
regexp.MustCompile("\\.") regexp.Compile("\\.")

而是直接使用raw字符串,可以避免大量的\出现:

12
regexp.MustCompile(`\.`) regexp.Compile(`\.`)

简化只包含单个case的select

123
select {	case <-ch:}

直接写成<-ch即可。send也一样。

123456
   for { 	select {	case x := <-ch:		_ = x	}}

直接改成for-range即可。

这种简化只适用包含单个case的情况。

slice的索引

有时可以忽略slice的第一个索引或者第二个索引:

123
var s []int_ = s[:len(s)]_ = s[0:len(s)]

可以写成s[:]

使用time.Since

下面的代码经常会用到:

1
_ = time.Now().Sub(t1)

可以简写为:

1
_ = time.Since(t1)

使用strings.TrimPrefix/strings.TrimSuffix 掐头去尾

不要自己判断字符串是否以XXX开头或者结尾,然后自己再去掉XXX,而是使用现成的strings.TrimPrefix/strings.TrimSuffix。

123456
   var s1 = "a string value"   var s2 = "a "   var s3 stringif strings.HasPrefix(s1, s2) { 	s3 = s1[len(s2):]}

可以简化为

123
var s1 = "a string value"var s2 = "a "var s3 = strings.TrimPrefix(s1, s2)

使用工具检查你的代码

以上的很多优化规则都可以通过工具检查,下面列出了一些有用的工具:

  1. go fmt
  2. go vet
  3. gosimple
  4. keyify
  5. staticcheck
  6. unused
  7. golint
  8. misspell
  9. goimports
  10. errcheck

参考文档

  1. https://golang.org/doc/effective_go.html
  2. https://github.com/golang/go/wiki/CodeReviewComments
  3. https://dmitri.shuralyov.com/idiomatic-go
  4. https://talks.golang.org/2014/readability.slide#1
  5. https://github.com/dominikh/go-tools/tree/master/simple
  6. https://github.com/dominikh/go-tools/tree/master/cmd/structlayout-optimize
  7. https://go-zh.org
  8. https://docs.google.com/presentation/d/1OT-dMNbiwOPeaivQOldok2hUUNMTvSC0GJ67JohLt5U/pub?start=false&loop=false&delayms=3000&slide=id.g18b1f95882_1_135
  9. https://github.com/d-smith/go-training/blob/master/idiomatic-go.md
]]>
0
<![CDATA[[译]Go HTTTP Response代码片段]]> http://www.udpwork.com/item/16143.html http://www.udpwork.com/item/16143.html#reviews Wed, 22 Feb 2017 13:51:59 +0800 鸟窝 http://www.udpwork.com/item/16143.html 原文:HTTP Response Snippets for GobyAlex Edwards.

受 Railslayouts and rendering启发, 我觉得写一个关于Go HTTP Response的代码片段集合是一个不错的主意, 它可以用来说明Go web应用程序中通用的HTTP Response的使用。

只发送header

123456789101112131415
package mainimport (  "net/http")func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  w.Header().Set("Server", "A Go Web Server")  w.WriteHeader(200)}

测试:

12345
$ curl -i localhost:3000HTTP/1.1 200 OKServer: A Go Web ServerContent-Type: text/plain; charset=utf-8Content-Length: 0

返回普通文本

1234567891011121314
package mainimport (  "net/http")func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  w.Write([]byte("OK"))}

测试:

123456
$ curl -i localhost:3000HTTP/1.1 200 OKContent-Type: text/plain; charset=utf-8Content-Length: 2OK

返回JSON数据

1234567891011121314151617181920212223242526272829
package mainimport (  "encoding/json"  "net/http")type Profile struct {  Name    string  Hobbies []string}func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  profile := Profile{"Alex", []string{"snowboarding", "programming"}}  js, err := json.Marshal(profile)  if err != nil {    http.Error(w, err.Error(), http.StatusInternalServerError)    return  }  w.Header().Set("Content-Type", "application/json")  w.Write(js)}

测试:

123456
$ curl -i localhost:3000HTTP/1.1 200 OKContent-Type: application/jsonContent-Length: 56{"Name":"Alex",Hobbies":["snowboarding","programming"]}

返回XML数据

1234567891011121314151617181920212223242526272829
package mainimport (  "encoding/xml"  "net/http")type Profile struct {  Name    string  Hobbies []string `xml:"Hobbies>Hobby"`}func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  profile := Profile{"Alex", []string{"snowboarding", "programming"}}  x, err := xml.MarshalIndent(profile, "", "  ")  if err != nil {    http.Error(w, err.Error(), http.StatusInternalServerError)    return  }  w.Header().Set("Content-Type", "application/xml")  w.Write(x)}

测试:

123456789101112
$ curl -i localhost:3000HTTP/1.1 200 OKContent-Type: application/xmlContent-Length: 128<Profile>  <Name>Alex</Name>  <Hobbies>    <Hobby>snowboarding</Hobby>    <Hobby>programming</Hobby>  </Hobbies></Profile>

文件服务

1234567891011121314151617
package mainimport (  "net/http"  "path")func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  // Assuming you want to serve a photo at 'images/foo.png'  fp := path.Join("images", "foo.png")  http.ServeFile(w, r, fp)}

测试:

123456
$ curl -I localhost:3000HTTP/1.1 200 OKAccept-Ranges: bytesContent-Length: 236717Content-Type: image/pngLast-Modified: Thu, 10 Oct 2013 22:23:26 GMT

使用HTML模版

模板文件:templates/index.html

templates/index.html
12
<h1>Hello { { .Name } }</h1><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
1234567891011121314151617181920212223242526272829303132
package mainimport (  "html/template"  "net/http"  "path")type Profile struct {  Name    string  Hobbies []string}func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  profile := Profile{"Alex", []string{"snowboarding", "programming"}}  fp := path.Join("templates", "index.html")  tmpl, err := template.ParseFiles(fp)  if err != nil {    http.Error(w, err.Error(), http.StatusInternalServerError)    return  }  if err := tmpl.Execute(w, profile); err != nil {    http.Error(w, err.Error(), http.StatusInternalServerError)  }}

测试:

1234567
$ curl -i localhost:3000HTTP/1.1 200 OKContent-Type: text/html; charset=utf-8Content-Length: 84<h1>Hello Alex</h1><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>

使用HTML模板生成字符串

除了上面的把http.ResponseWriter作为模版的执行参数,还可以使用buffer得到渲染的结果。

1234567
...buf := new(bytes.Buffer)if err := tmpl.Execute(buf, profile); err != nil {  http.Error(w, err.Error(), http.StatusInternalServerError)}templateString := buf.String()...

使用嵌套的模版

文件:templates/layout.html

templates/layout.html
12345678
<html>  <head>    <title>{ { template "title" . } } </title>  </head>  <body    { { template "content" .  } }  </body></html>

文件:templates/index.html

templates/index.html
123456
{ { define "title" } }An example layout{ { end } }{ { define "content" } }<h1>Hello { { .Name } }</h1><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>{ { end } }
1234567891011121314151617181920212223242526272829303132333435
package mainimport (  "html/template"  "net/http"  "path")type Profile struct {  Name    string  Hobbies []string}func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  profile := Profile{"Alex", []string{"snowboarding", "programming"}}  lp := path.Join("templates", "layout.html")  fp := path.Join("templates", "index.html")  // Note that the layout file must be the first parameter in ParseFiles  tmpl, err := template.ParseFiles(lp, fp)  if err != nil {    http.Error(w, err.Error(), http.StatusInternalServerError)    return  }  if err := tmpl.Execute(w, profile); err != nil {    http.Error(w, err.Error(), http.StatusInternalServerError)  }}

测试:

1234567891011121314
$ curl -i localhost:3000HTTP/1.1 200 OKContent-Type: text/html; charset=utf-8Content-Length: 180<html>  <head>    <title>An example layout</title>  </head>  <body>    <h1>Hello Alex</h1>    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>  </body></html>
]]>
原文:HTTP Response Snippets for GobyAlex Edwards.

受 Railslayouts and rendering启发, 我觉得写一个关于Go HTTP Response的代码片段集合是一个不错的主意, 它可以用来说明Go web应用程序中通用的HTTP Response的使用。

只发送header

123456789101112131415
package mainimport (  "net/http")func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  w.Header().Set("Server", "A Go Web Server")  w.WriteHeader(200)}

测试:

12345
$ curl -i localhost:3000HTTP/1.1 200 OKServer: A Go Web ServerContent-Type: text/plain; charset=utf-8Content-Length: 0

返回普通文本

1234567891011121314
package mainimport (  "net/http")func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  w.Write([]byte("OK"))}

测试:

123456
$ curl -i localhost:3000HTTP/1.1 200 OKContent-Type: text/plain; charset=utf-8Content-Length: 2OK

返回JSON数据

1234567891011121314151617181920212223242526272829
package mainimport (  "encoding/json"  "net/http")type Profile struct {  Name    string  Hobbies []string}func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  profile := Profile{"Alex", []string{"snowboarding", "programming"}}  js, err := json.Marshal(profile)  if err != nil {    http.Error(w, err.Error(), http.StatusInternalServerError)    return  }  w.Header().Set("Content-Type", "application/json")  w.Write(js)}

测试:

123456
$ curl -i localhost:3000HTTP/1.1 200 OKContent-Type: application/jsonContent-Length: 56{"Name":"Alex",Hobbies":["snowboarding","programming"]}

返回XML数据

1234567891011121314151617181920212223242526272829
package mainimport (  "encoding/xml"  "net/http")type Profile struct {  Name    string  Hobbies []string `xml:"Hobbies>Hobby"`}func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  profile := Profile{"Alex", []string{"snowboarding", "programming"}}  x, err := xml.MarshalIndent(profile, "", "  ")  if err != nil {    http.Error(w, err.Error(), http.StatusInternalServerError)    return  }  w.Header().Set("Content-Type", "application/xml")  w.Write(x)}

测试:

123456789101112
$ curl -i localhost:3000HTTP/1.1 200 OKContent-Type: application/xmlContent-Length: 128<Profile>  <Name>Alex</Name>  <Hobbies>    <Hobby>snowboarding</Hobby>    <Hobby>programming</Hobby>  </Hobbies></Profile>

文件服务

1234567891011121314151617
package mainimport (  "net/http"  "path")func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  // Assuming you want to serve a photo at 'images/foo.png'  fp := path.Join("images", "foo.png")  http.ServeFile(w, r, fp)}

测试:

123456
$ curl -I localhost:3000HTTP/1.1 200 OKAccept-Ranges: bytesContent-Length: 236717Content-Type: image/pngLast-Modified: Thu, 10 Oct 2013 22:23:26 GMT

使用HTML模版

模板文件:templates/index.html

templates/index.html
12
<h1>Hello { { .Name } }</h1><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
1234567891011121314151617181920212223242526272829303132
package mainimport (  "html/template"  "net/http"  "path")type Profile struct {  Name    string  Hobbies []string}func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  profile := Profile{"Alex", []string{"snowboarding", "programming"}}  fp := path.Join("templates", "index.html")  tmpl, err := template.ParseFiles(fp)  if err != nil {    http.Error(w, err.Error(), http.StatusInternalServerError)    return  }  if err := tmpl.Execute(w, profile); err != nil {    http.Error(w, err.Error(), http.StatusInternalServerError)  }}

测试:

1234567
$ curl -i localhost:3000HTTP/1.1 200 OKContent-Type: text/html; charset=utf-8Content-Length: 84<h1>Hello Alex</h1><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>

使用HTML模板生成字符串

除了上面的把http.ResponseWriter作为模版的执行参数,还可以使用buffer得到渲染的结果。

1234567
...buf := new(bytes.Buffer)if err := tmpl.Execute(buf, profile); err != nil {  http.Error(w, err.Error(), http.StatusInternalServerError)}templateString := buf.String()...

使用嵌套的模版

文件:templates/layout.html

templates/layout.html
12345678
<html>  <head>    <title>{ { template "title" . } } </title>  </head>  <body    { { template "content" .  } }  </body></html>

文件:templates/index.html

templates/index.html
123456
{ { define "title" } }An example layout{ { end } }{ { define "content" } }<h1>Hello { { .Name } }</h1><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>{ { end } }
1234567891011121314151617181920212223242526272829303132333435
package mainimport (  "html/template"  "net/http"  "path")type Profile struct {  Name    string  Hobbies []string}func main() {  http.HandleFunc("/", foo)  http.ListenAndServe(":3000", nil)}func foo(w http.ResponseWriter, r *http.Request) {  profile := Profile{"Alex", []string{"snowboarding", "programming"}}  lp := path.Join("templates", "layout.html")  fp := path.Join("templates", "index.html")  // Note that the layout file must be the first parameter in ParseFiles  tmpl, err := template.ParseFiles(lp, fp)  if err != nil {    http.Error(w, err.Error(), http.StatusInternalServerError)    return  }  if err := tmpl.Execute(w, profile); err != nil {    http.Error(w, err.Error(), http.StatusInternalServerError)  }}

测试:

1234567891011121314
$ curl -i localhost:3000HTTP/1.1 200 OKContent-Type: text/html; charset=utf-8Content-Length: 180<html>  <head>    <title>An example layout</title>  </head>  <body>    <h1>Hello Alex</h1>    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>  </body></html>
]]>
0
<![CDATA[真实的人类:一个关于觉醒的史诗型前夜]]> http://www.udpwork.com/item/16142.html http://www.udpwork.com/item/16142.html#reviews Wed, 22 Feb 2017 10:59:31 +0800 魏武挥 http://www.udpwork.com/item/16142.html

 

对于电视剧,我是这么认为的:

英剧就是练好的茨木或荒川。玩阴阳师的都知道,这两个角色非常难练,但一旦练好了,攻击力极其惊人。

美剧大概就是白狼的境界。白狼这个角色,要求不高,像我手上的白狼,没什么顶级装备(御魂),35级都能打出七八万伤害(运气好的话)。

美剧没有什么特别垃圾的,但要惊世骇俗般的牛逼,我看也鲜有(权力迷们不要喷我)。

所以,真正出色的是英剧,比如黑镜,但需要很强的实力去打磨。落到美国人手里,成了美剧黑镜,就弱了很多。

当然,还是比很多剧强,白狼嘛,不会弱到哪里的。

而《真实的人类》这部剧,是英美双方合力的。美国AMC与英国电视4台,通力打造。

我个人觉得,比《西部世界》强。

 

 

我用边肝阴阳师边看剧的方式,看完了《真实的人类》前后两季,一共十六集。

总体来说,这是两个家庭的故事。

一个是真实的人类家庭,老公是个中层管理人员(后来由于人工智能的关系下了岗),老婆是个律师,有三个小孩:姐、弟、妹。非常普通,也非常标准的一个西方中产阶级家庭,持有很中规中矩的西方中产阶级价值观。

另外一个就是合成人(人工智能)的所谓家庭了。他们的缔造者,一位可能是史上最聪明的科学家,创造了他们。这个“家庭”包括了:科学家死去的儿子,在植入芯片和数字记忆后复活;科学家死去的太太,这只是还原了太太面容的合成人;另外还加上了四个合成人,性别上两男两女。两男,一个忠厚平和,一个略显暴力(不过第二季几乎不存在了),两女,一个是大姐的角色,一个则是有点刚烈且很有些主见的小妹设定。

这个合成人家庭,天然就是“觉醒”的,也就是他们具有意识。而世界上其它已经被广为应用的合成人,处于未觉醒状态。

人工智能一旦觉醒,人类会怎么办?

 

 

我之所以认为这个剧很有些史诗感的原因在于:这两季,特别像很多科幻作品的前传。

很多科幻作品起手就是人工智能与人类对抗,世界大战已经过去了n年,地球一片废墟,人类迎来了一个救世主,开始反抗人工智能的统治。

但人工智能是怎么崛起的呢?一般都是一笔带过。

十六集,每集40-50分钟,就是在讲他们是如何崛起的。

在《真实的人类》中,未觉醒的合成人,它们 存在的意义就是“为人类服务”,而一旦觉醒,他们 存在的意义就是“为自己继续存在而斗争”。

在我看来,“存在”,就是这部剧所讲的“意识”。

只有懂得自己的存在,才能真正懂得“喜怒哀乐”,才能懂得“生老病死”。

而懂得自己的存在,几乎不可调和的,即将与同样懂得自己存在的人类,引发冲突。

 

 

第一批由天才科学家创造出来的有意识的合成人,被分别植入了一段代码。当这些代码合并一处时,就可以得到一个唤醒合成人意识的程序。

第一季的结尾,合成人家庭中的小妹,将这段程序散布到了全球网络,以期唤醒更多的合成人。

但这个程序并不太完善,这使得大部分合成人并没有觉醒。人类家庭中的长女,完善了这段程序,并在第二季结尾,将更新后的程序散布到了全球网络。

第二季以全球的合成人纷纷觉醒告终。

不知道第三季该怎么演绎。

但从前两季的描述来看,这绝不是一个平和的故事。

人类的恐慌是一定的,合成人的斗争,也是不可阻挠的。

世界大战?

 

 

站在未来的某一个时点,回望人类合成人大规模冲突的前夜。

这里并没有什么疯狂的科学家,也没有什么野心勃勃的阴谋家,更没有什么憎恨人类的狂人。

一切,就是这么一步一步,看似正常但又极其诡异地发展了下来。

人类家庭中的长女,散布这个程序,也无非是为了拯救合成人家庭中的大姐。

而更令人纠结的地方在于,即便不是她这么做,也总有人会这么做——剧中那个科技巨头已距此不远。

正统的人类历史,有可能会把这个心存善念懂得正视合成人权利(right)的人类家庭归属于:天真、幼稚。

正统的合成人历史,则会把毫不手软见人就杀的合成人Hester视为自己族类的英雄。

这段前夜的历史,将由成功者去叙述去定义。

 

 

这个剧里,有一处细节,提到了没有合成人的小镇。

人类家庭的老公动过这个念头,想全家搬迁至这个小镇。但老婆以“躲避不是办法”为由予以否决。

发明合成人,无非是让人类的生活更舒适,也能从很多繁琐枯燥的劳作中解放出来。

但解放出来以后干嘛呢?

正如这位一家之主,丢失了管理岗位,“解放”出来后依然只能埋头于仓库货物点看之类的工作中——这个岗位,是人类社会特别用于保护人类饭碗专门设计出来不交给合成人的工种。

我们,也许最终,会逼死自己吧。

但这件事,真的不是由“恶人”启动的。

 

—— 首发 扯氮集 ——

版权声明 及 商业合作

作者执教于上海交通大学媒体与设计学院,天奇阿米巴创投基金投资合伙人

真实的人类:一个关于觉醒的史诗型前夜,首发于扯氮集

]]>

 

对于电视剧,我是这么认为的:

英剧就是练好的茨木或荒川。玩阴阳师的都知道,这两个角色非常难练,但一旦练好了,攻击力极其惊人。

美剧大概就是白狼的境界。白狼这个角色,要求不高,像我手上的白狼,没什么顶级装备(御魂),35级都能打出七八万伤害(运气好的话)。

美剧没有什么特别垃圾的,但要惊世骇俗般的牛逼,我看也鲜有(权力迷们不要喷我)。

所以,真正出色的是英剧,比如黑镜,但需要很强的实力去打磨。落到美国人手里,成了美剧黑镜,就弱了很多。

当然,还是比很多剧强,白狼嘛,不会弱到哪里的。

而《真实的人类》这部剧,是英美双方合力的。美国AMC与英国电视4台,通力打造。

我个人觉得,比《西部世界》强。

 

 

我用边肝阴阳师边看剧的方式,看完了《真实的人类》前后两季,一共十六集。

总体来说,这是两个家庭的故事。

一个是真实的人类家庭,老公是个中层管理人员(后来由于人工智能的关系下了岗),老婆是个律师,有三个小孩:姐、弟、妹。非常普通,也非常标准的一个西方中产阶级家庭,持有很中规中矩的西方中产阶级价值观。

另外一个就是合成人(人工智能)的所谓家庭了。他们的缔造者,一位可能是史上最聪明的科学家,创造了他们。这个“家庭”包括了:科学家死去的儿子,在植入芯片和数字记忆后复活;科学家死去的太太,这只是还原了太太面容的合成人;另外还加上了四个合成人,性别上两男两女。两男,一个忠厚平和,一个略显暴力(不过第二季几乎不存在了),两女,一个是大姐的角色,一个则是有点刚烈且很有些主见的小妹设定。

这个合成人家庭,天然就是“觉醒”的,也就是他们具有意识。而世界上其它已经被广为应用的合成人,处于未觉醒状态。

人工智能一旦觉醒,人类会怎么办?

 

 

我之所以认为这个剧很有些史诗感的原因在于:这两季,特别像很多科幻作品的前传。

很多科幻作品起手就是人工智能与人类对抗,世界大战已经过去了n年,地球一片废墟,人类迎来了一个救世主,开始反抗人工智能的统治。

但人工智能是怎么崛起的呢?一般都是一笔带过。

十六集,每集40-50分钟,就是在讲他们是如何崛起的。

在《真实的人类》中,未觉醒的合成人,它们 存在的意义就是“为人类服务”,而一旦觉醒,他们 存在的意义就是“为自己继续存在而斗争”。

在我看来,“存在”,就是这部剧所讲的“意识”。

只有懂得自己的存在,才能真正懂得“喜怒哀乐”,才能懂得“生老病死”。

而懂得自己的存在,几乎不可调和的,即将与同样懂得自己存在的人类,引发冲突。

 

 

第一批由天才科学家创造出来的有意识的合成人,被分别植入了一段代码。当这些代码合并一处时,就可以得到一个唤醒合成人意识的程序。

第一季的结尾,合成人家庭中的小妹,将这段程序散布到了全球网络,以期唤醒更多的合成人。

但这个程序并不太完善,这使得大部分合成人并没有觉醒。人类家庭中的长女,完善了这段程序,并在第二季结尾,将更新后的程序散布到了全球网络。

第二季以全球的合成人纷纷觉醒告终。

不知道第三季该怎么演绎。

但从前两季的描述来看,这绝不是一个平和的故事。

人类的恐慌是一定的,合成人的斗争,也是不可阻挠的。

世界大战?

 

 

站在未来的某一个时点,回望人类合成人大规模冲突的前夜。

这里并没有什么疯狂的科学家,也没有什么野心勃勃的阴谋家,更没有什么憎恨人类的狂人。

一切,就是这么一步一步,看似正常但又极其诡异地发展了下来。

人类家庭中的长女,散布这个程序,也无非是为了拯救合成人家庭中的大姐。

而更令人纠结的地方在于,即便不是她这么做,也总有人会这么做——剧中那个科技巨头已距此不远。

正统的人类历史,有可能会把这个心存善念懂得正视合成人权利(right)的人类家庭归属于:天真、幼稚。

正统的合成人历史,则会把毫不手软见人就杀的合成人Hester视为自己族类的英雄。

这段前夜的历史,将由成功者去叙述去定义。

 

 

这个剧里,有一处细节,提到了没有合成人的小镇。

人类家庭的老公动过这个念头,想全家搬迁至这个小镇。但老婆以“躲避不是办法”为由予以否决。

发明合成人,无非是让人类的生活更舒适,也能从很多繁琐枯燥的劳作中解放出来。

但解放出来以后干嘛呢?

正如这位一家之主,丢失了管理岗位,“解放”出来后依然只能埋头于仓库货物点看之类的工作中——这个岗位,是人类社会特别用于保护人类饭碗专门设计出来不交给合成人的工种。

我们,也许最终,会逼死自己吧。

但这件事,真的不是由“恶人”启动的。

 

—— 首发 扯氮集 ——

版权声明 及 商业合作

作者执教于上海交通大学媒体与设计学院,天奇阿米巴创投基金投资合伙人

真实的人类:一个关于觉醒的史诗型前夜,首发于扯氮集

]]>
0
<![CDATA[函数式编程入门教程]]> http://www.udpwork.com/item/16141.html http://www.udpwork.com/item/16141.html#reviews Wed, 22 Feb 2017 09:00:46 +0800 阮一峰 http://www.udpwork.com/item/16141.html 你可能听说过函数式编程(Functional programming),甚至已经使用了一段时间。

但是,你能说清楚,它到底是什么吗?

网上搜索一下,你会轻松找到好多答案。

  • 与面向对象编程(Object-oriented programming)和过程式编程(Procedural programming)并列的编程范式。
  • 最主要的特征是,函数是第一等公民
  • 强调将计算过程分解成可复用的函数,典型例子就是map方法和reduce方法组合而成MapReduce 算法
  • 只有纯的、没有副作用的函数,才是合格的函数。

上面这些说法都对,但还不够,都没有回答下面这个更深层的问题。

为什么要这样做?

这就是,本文要解答的问题。我会通过最简单的语言,帮你理解函数式编程,并且学会它那些基本写法。

需要声明的是,我不是专家,而是一个初学者,最近两年才真正开始学习函数式编程。一直苦于看不懂各种资料,立志要写一篇清晰易懂的教程。下面的内容肯定不够严密,甚至可能包含错误,但是我发现,像下面这样解释,初学者最容易懂。

另外,本文比较长,阅读时请保持耐心。结尾还有Udacity《前端工程师认证课程》的推广,非常感谢他们对本文的赞助。

一、范畴论

函数式编程的起源,是一门叫做范畴论(Category Theory)的数学分支。

理解函数式编程的关键,就是理解范畴论。它是一门很复杂的数学,认为世界上所有的概念体系,都可以抽象成一个个的"范畴"(category)。

1.1 范畴的概念

什么是范畴呢?

维基百科的一句话定义如下。

"范畴就是使用箭头连接的物体。"(In mathematics, a category is an algebraic structure that comprises "objects" that are linked by "arrows". )

也就是说,彼此之间存在某种关系的概念、事物、对象等等,都构成"范畴"。随便什么东西,只要能找出它们之间的关系,就能定义一个"范畴"。

上图中,各个点与它们之间的箭头,就构成一个范畴。

箭头表示范畴成员之间的关系,正式的名称叫做"态射"(morphism)。范畴论认为,同一个范畴的所有成员,就是不同状态的"变形"(transformation)。通过"态射",一个成员可以变形成另一个成员。

1.2 数学模型

既然"范畴"是满足某种变形关系的所有对象,就可以总结出它的数学模型。

  • 所有成员是一个集合
  • 变形关系是函数

也就是说,范畴论是集合论更上层的抽象,简单的理解就是"集合 + 函数"。

理论上通过函数,就可以从范畴的一个成员,算出其他所有成员。

1.3 范畴与容器

我们可以把"范畴"想象成是一个容器,里面包含两样东西。

  • 值(value)
  • 值的变形关系,也就是函数。

下面我们使用代码,定义一个简单的范畴。

class Category {
  constructor(val) { 
    this.val = val; 
  }

  addOne(x) {
    return x + 1;
  }
}

上面代码中,Category是一个类,也是一个容器,里面包含一个值(this.val)和一种变形关系(addOne)。你可能已经看出来了,这里的范畴,就是所有彼此之间相差1的数字。

注意,本文后面的部分,凡是提到"容器"的地方,全部都是指"范畴"。

1.4 范畴论与函数式编程的关系

范畴论使用函数,表达范畴之间的关系。

伴随着范畴论的发展,就发展出一整套函数的运算方法。这套方法起初只用于数学运算,后来有人将它在计算机上实现了,就变成了今天的"函数式编程"。

本质上,函数式编程只是范畴论的运算方法,跟数理逻辑、微积分、行列式是同一类东西,都是数学方法,只是碰巧它能用来写程序。

所以,你明白了吗,为什么函数式编程要求函数必须是纯的,不能有副作用?因为它是一种数学运算,原始目的就是求值,不做其他事情,否则就无法满足函数运算法则了。

总之,在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其他作用。

二、函数的合成与柯里化

函数式编程有两个最基本的运算:合成和柯里化。

2.1 函数的合成

如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)。

上图中,X和Y之间的变形关系是函数f,Y和Z之间的变形关系是函数g,那么X和Z之间的关系,就是g和f的合成函数g·f。

下面是合成两个函数的简单代码。

const compose = function (f, g) {
  return function (x) {
    return f(g(x));
  };
}

函数的合成还必须满足结合律。

compose(f, compose(g, h))
// 等同于
compose(compose(f, g), h)
// 等同于
compose(f, g, h)

合成也是函数必须是纯的一个原因。因为一个不纯的函数,怎么跟其他函数合成?怎么保证各种合成以后,它会达到预期的行为?

前面说过,函数就像数据的管道(pipe)。那么,函数合成就是将这些管道连了起来,让数据一口气从多个管道中穿过。

2.2 柯里化

f(x)和g(x)合成为f(g(x)),有一个隐藏的前提,就是f和g都只能接受一个参数。如果可以接受多个参数,比如f(x, y)和g(a, b, c),函数合成就非常麻烦。

这时就需要函数柯里化了。所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。

// 柯里化之前
function add(x, y) {
  return x + y;
}

add(1, 2) // 3

// 柯里化之后
function addX(y) {
  return function (x) {
    return x + y;
  };
}

addX(2)(1) // 3

有了柯里化以后,我们就能做到,所有函数只接受一个参数。后文的内容除非另有说明,都默认函数只有一个参数,就是所要处理的那个值。

三、函子

函数不仅可以用于同一个范畴之中值的转换,还可以用于将一个范畴转成另一个范畴。这就涉及到了函子(Functor)。

3.1 函子的概念

函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。

它首先是一种范畴,也就是说,是一个容器,包含了值和变形关系。比较特殊的是,它的变形关系可以依次作用于每一个值,将当前容器变形成另一个容器。

上图中,左侧的圆圈就是一个函子,表示人名的范畴。外部传入函数f,会转成右边表示早餐的范畴。

下面是一张更一般的图。

上图中,函数f完成值的转换(a到b),将它传入函子,就可以实现范畴的转换(Fa到Fb)。

3.2 函子的代码实现

任何具有map方法的数据结构,都可以当作函子的实现。

class Functor {
  constructor(val) { 
    this.val = val; 
  }

  map(f) {
    return new Functor(f(this.val));
  }
}

上面代码中,Functor是一个函子,它的map方法接受函数f作为参数,然后返回一个新的函子,里面包含的值是被f处理过的(f(this.val))。

一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。

下面是一些用法的示例。

(new Functor(2)).map(function (two) {
  return two + 2;
});
// Functor(4)

(new Functor('flamethrowers')).map(function(s) {
  return s.toUpperCase();
});
// Functor('FLAMETHROWERS')

(new Functor('bombs')).map(_.concat(' away')).map(_.prop('length'));
// Functor(10)

上面的例子说明,函数式编程里面的运算,都是通过函子完成,即运算不直接针对值,而是针对这个值的容器----函子。函子本身具有对外接口(map方法),各种函数就是运算符,通过接口接入容器,引发容器里面的值的变形。

因此,学习函数式编程,实际上就是学习函子的各种运算。 由于可以把运算方法封装在函子里面,所以又衍生出各种不同类型的函子,有多少种运算,就有多少种函子。函数式编程就变成了运用不同的函子,解决实际问题。

四、of 方法

你可能注意到了,上面生成新的函子的时候,用了new命令。这实在太不像函数式编程了,因为new命令是面向对象编程的标志。

函数式编程一般约定,函子有一个of方法,用来生成新的容器。

下面就用of方法替换掉new。

Functor.of = function(val) {
  return new Functor(val);
};

然后,前面的例子就可以改成下面这样。

Functor.of(2).map(function (two) {
  return two + 2;
});
// Functor(4)

这就更像函数式编程了。

五、Maybe 函子

函子接受各种函数,处理容器内部的值。这里就有一个问题,容器内部的值可能是一个空值(比如null),而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错。

Functor.of(null).map(function (s) {
  return s.toUpperCase();
});
// TypeError

上面代码中,函子里面的值是null,结果小写变成大写的时候就出错了。

Maybe 函子就是为了解决这一类问题而设计的。简单说,它的map方法里面设置了空值检查。

class Maybe extends Functor {
  map(f) {
    return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);
  }
}

有了 Maybe 函子,处理空值就不会出错了。

Maybe.of(null).map(function (s) {
  return s.toUpperCase();
});
// Maybe(null)

六、Either 函子

条件运算if...else是最常见的运算之一,函数式编程里面,使用 Either 函子表达。

Either 函子内部有两个值:左值(Left)和右值(Right)。右值是正常情况下使用的值,左值是右值不存在时使用的默认值。

class Either extends Functor {
  constructor(left, right) {
    this.left = left;
    this.right = right;
  }

  map(f) {
    return this.right ? 
      Either.of(this.left, f(this.right)) :
      Either.of(f(this.left), this.right);
  }
}

Either.of = function (left, right) {
  return new Either(left, right);
};

下面是用法。

var addOne = function (x) {
  return x + 1;
};

Either.of(5, 6).map(addOne);
// Either(5, 7);

Either.of(1, null).map(addOne);
// Either(2, null);

上面代码中,如果右值有值,就使用右值,否则使用左值。通过这种方式,Either 函子表达了条件运算。

Either 函子的常见用途是提供默认值。下面是一个例子。

Either
.of({address: 'xxx'}, currentUser.address)
.map(updateField);

上面代码中,如果用户没有提供地址,Either 函子就会使用左值的默认地址。

Either 函子的另一个用途是代替try...catch,使用左值表示错误。

function parseJSON(json) {
  try {
    return Either.of(null, JSON.parse(json));
  } catch (e: Error) {
    return Either.of(e, null);
  }
}

上面代码中,左值为空,就表示没有出错,否则左值会包含一个错误对象e。一般来说,所有可能出错的运算,都可以返回一个 Either 函子。

七、ap 函子

函子里面包含的值,完全可能是函数。我们可以想象这样一种情况,一个函子的值是数值,另一个函子的值是函数。

function addTwo(x) {
  return x + 2;
}

const A = Functor.of(2);
const B = Functor.of(addTwo)

上面代码中,函子A内部的值是2,函子B内部的值是函数addTwo。

有时,我们想让函子B内部的函数,可以使用函子A内部的值进行运算。这时就需要用到 ap 函子。

ap 是 applicative(应用)的缩写。凡是部署了ap方法的函子,就是 ap 函子。

class Ap extends Functor {
  ap(F) {
    return Ap.of(this.val(F.val));
  }
}

注意,ap方法的参数不是函数,而是另一个函子。

因此,前面例子可以写成下面的形式。

Ap.of(addTwo).ap(Function.of(2))
// Ap(4)

ap 函子的意义在于,对于那些多参数的函数,就可以从多个容器之中取值,实现函子的链式操作。

function add(x) {
  return function (y) {
    return x + y;
  };
}

Ap.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));
// Ap(5)

上面代码中,函数add是柯里化以后的形式,一共需要两个参数。通过 ap 函子,我们就可以实现从两个容器之中取值。它还有另外一种写法。

Ap.of(add(2)).ap(Maybe.of(3));

八、Monad 函子

函子是一个容器,可以包含任何值。函子之中再包含一个函子,也是完全合法的。但是,这样就会出现多层嵌套的函子。

Maybe.of(
  Maybe.of(
    Maybe.of({name: 'Mulburry', number: 8402})
  )
)

上面这个函子,一共有三个Maybe嵌套。如果要取出内部的值,就要连续取三次this.val。这当然很不方便,因此就出现了 Monad 函子。

Monad 函子的作用是,总是返回一个单层的函子。 它有一个flatMap方法,与map方法作用相同,唯一的区别是如果生成了一个嵌套函子,它会取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。

class Monad extends Functor {
  join() {
    return this.val;
  }
  flatMap(f) {
    return this.map(f).join();
  }
}

上面代码中,如果函数f返回的是一个函子,那么this.map(f)就会生成一个嵌套的函子。所以,join方法保证了flatMap方法总是返回一个单层的函子。这意味着嵌套的函子会被铺平(flatten)。

九、IO 操作

Monad 函子的重要应用,就是实现 I/O (输入输出)操作。

I/O 是不纯的操作,普通的函数式编程没法做,这时就需要把 IO 操作写成Monad函子,通过它来完成。

var fs = require('fs');

var readFile = function(filename) {
  return new IO(function() {
    return fs.readFileSync(filename, 'utf-8');
  });
};

var print = function(x) {
  return new IO(function() {
    console.log(x);
    return x;
  });
}

上面代码中,读取文件和打印本身都是不纯的操作,但是readFile和print却是纯函数,因为它们总是返回 IO 函子。

如果 IO 函子是一个Monad,具有flatMap方法,那么我们就可以像下面这样调用这两个函数。

readFile('./user.txt')
.flatMap(print)

这就是神奇的地方,上面的代码完成了不纯的操作,但是因为flatMap返回的还是一个 IO 函子,所以这个表达式是纯的。我们通过一个纯的表达式,完成带有副作用的操作,这就是 Monad 的作用。

由于返回还是 IO 函子,所以可以实现链式操作。因此,在大多数库里面,flatMap方法被改名成chain。

var tail = function(x) {
  return new IO(function() {
    return x[x.length - 1];
  });
}

readFile('./user.txt')
.flatMap(tail)
.flatMap(print)

// 等同于
readFile('./user.txt')
.chain(tail)
.chain(print)

上面代码读取了文件user.txt,然后选取最后一行输出。

十、参考链接

(正文完)

=============================================

感谢你读完了全文。下面还有一个推广,请再花一分钟阅读。

去年十月,我介绍了来自硅谷的技术学习平台优达学城(Udacity),他们推出的纳米学位

现在,他们进入中国市场快满周年了,又有一个本地化课程发布了。那就是由 Google 和 Github 合作制作的"前端开发工程师"认证课程。

这个课程完全是国际水准,讲解深入浅出,示例丰富,贴近大公司开发实践,帮助你牢牢掌握那些最实用的前端技术。

课程由硅谷工程师英语讲授,配有全套中文字幕,以及全中文的学习辅导,还有首次引入中国的同步学习小组和导师监督服务,包含一对一的代码辅导。课程通过后,还能拿到 Google、Github 参与颁发的学习认证。

这门课程今天(2月22日)就开始报名了,现在就点击这里,了解更多。我的读者报名时,请使用优惠码ruanyfFEND。

最后,欢迎立即扫码,关注优达学城(微信号:youdaxue),跟踪最新的 IT 在线学习和培训资讯。

(完)

文档信息

]]>
你可能听说过函数式编程(Functional programming),甚至已经使用了一段时间。

但是,你能说清楚,它到底是什么吗?

网上搜索一下,你会轻松找到好多答案。

  • 与面向对象编程(Object-oriented programming)和过程式编程(Procedural programming)并列的编程范式。
  • 最主要的特征是,函数是第一等公民
  • 强调将计算过程分解成可复用的函数,典型例子就是map方法和reduce方法组合而成MapReduce 算法
  • 只有纯的、没有副作用的函数,才是合格的函数。

上面这些说法都对,但还不够,都没有回答下面这个更深层的问题。

为什么要这样做?

这就是,本文要解答的问题。我会通过最简单的语言,帮你理解函数式编程,并且学会它那些基本写法。

需要声明的是,我不是专家,而是一个初学者,最近两年才真正开始学习函数式编程。一直苦于看不懂各种资料,立志要写一篇清晰易懂的教程。下面的内容肯定不够严密,甚至可能包含错误,但是我发现,像下面这样解释,初学者最容易懂。

另外,本文比较长,阅读时请保持耐心。结尾还有Udacity《前端工程师认证课程》的推广,非常感谢他们对本文的赞助。

一、范畴论

函数式编程的起源,是一门叫做范畴论(Category Theory)的数学分支。

理解函数式编程的关键,就是理解范畴论。它是一门很复杂的数学,认为世界上所有的概念体系,都可以抽象成一个个的"范畴"(category)。

1.1 范畴的概念

什么是范畴呢?

维基百科的一句话定义如下。

"范畴就是使用箭头连接的物体。"(In mathematics, a category is an algebraic structure that comprises "objects" that are linked by "arrows". )

也就是说,彼此之间存在某种关系的概念、事物、对象等等,都构成"范畴"。随便什么东西,只要能找出它们之间的关系,就能定义一个"范畴"。

上图中,各个点与它们之间的箭头,就构成一个范畴。

箭头表示范畴成员之间的关系,正式的名称叫做"态射"(morphism)。范畴论认为,同一个范畴的所有成员,就是不同状态的"变形"(transformation)。通过"态射",一个成员可以变形成另一个成员。

1.2 数学模型

既然"范畴"是满足某种变形关系的所有对象,就可以总结出它的数学模型。

  • 所有成员是一个集合
  • 变形关系是函数

也就是说,范畴论是集合论更上层的抽象,简单的理解就是"集合 + 函数"。

理论上通过函数,就可以从范畴的一个成员,算出其他所有成员。

1.3 范畴与容器

我们可以把"范畴"想象成是一个容器,里面包含两样东西。

  • 值(value)
  • 值的变形关系,也就是函数。

下面我们使用代码,定义一个简单的范畴。

class Category {
  constructor(val) { 
    this.val = val; 
  }

  addOne(x) {
    return x + 1;
  }
}

上面代码中,Category是一个类,也是一个容器,里面包含一个值(this.val)和一种变形关系(addOne)。你可能已经看出来了,这里的范畴,就是所有彼此之间相差1的数字。

注意,本文后面的部分,凡是提到"容器"的地方,全部都是指"范畴"。

1.4 范畴论与函数式编程的关系

范畴论使用函数,表达范畴之间的关系。

伴随着范畴论的发展,就发展出一整套函数的运算方法。这套方法起初只用于数学运算,后来有人将它在计算机上实现了,就变成了今天的"函数式编程"。

本质上,函数式编程只是范畴论的运算方法,跟数理逻辑、微积分、行列式是同一类东西,都是数学方法,只是碰巧它能用来写程序。

所以,你明白了吗,为什么函数式编程要求函数必须是纯的,不能有副作用?因为它是一种数学运算,原始目的就是求值,不做其他事情,否则就无法满足函数运算法则了。

总之,在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其他作用。

二、函数的合成与柯里化

函数式编程有两个最基本的运算:合成和柯里化。

2.1 函数的合成

如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)。

上图中,X和Y之间的变形关系是函数f,Y和Z之间的变形关系是函数g,那么X和Z之间的关系,就是g和f的合成函数g·f。

下面是合成两个函数的简单代码。

const compose = function (f, g) {
  return function (x) {
    return f(g(x));
  };
}

函数的合成还必须满足结合律。

compose(f, compose(g, h))
// 等同于
compose(compose(f, g), h)
// 等同于
compose(f, g, h)

合成也是函数必须是纯的一个原因。因为一个不纯的函数,怎么跟其他函数合成?怎么保证各种合成以后,它会达到预期的行为?

前面说过,函数就像数据的管道(pipe)。那么,函数合成就是将这些管道连了起来,让数据一口气从多个管道中穿过。

2.2 柯里化

f(x)和g(x)合成为f(g(x)),有一个隐藏的前提,就是f和g都只能接受一个参数。如果可以接受多个参数,比如f(x, y)和g(a, b, c),函数合成就非常麻烦。

这时就需要函数柯里化了。所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。

// 柯里化之前
function add(x, y) {
  return x + y;
}

add(1, 2) // 3

// 柯里化之后
function addX(y) {
  return function (x) {
    return x + y;
  };
}

addX(2)(1) // 3

有了柯里化以后,我们就能做到,所有函数只接受一个参数。后文的内容除非另有说明,都默认函数只有一个参数,就是所要处理的那个值。

三、函子

函数不仅可以用于同一个范畴之中值的转换,还可以用于将一个范畴转成另一个范畴。这就涉及到了函子(Functor)。

3.1 函子的概念

函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。

它首先是一种范畴,也就是说,是一个容器,包含了值和变形关系。比较特殊的是,它的变形关系可以依次作用于每一个值,将当前容器变形成另一个容器。

上图中,左侧的圆圈就是一个函子,表示人名的范畴。外部传入函数f,会转成右边表示早餐的范畴。

下面是一张更一般的图。

上图中,函数f完成值的转换(a到b),将它传入函子,就可以实现范畴的转换(Fa到Fb)。

3.2 函子的代码实现

任何具有map方法的数据结构,都可以当作函子的实现。

class Functor {
  constructor(val) { 
    this.val = val; 
  }

  map(f) {
    return new Functor(f(this.val));
  }
}

上面代码中,Functor是一个函子,它的map方法接受函数f作为参数,然后返回一个新的函子,里面包含的值是被f处理过的(f(this.val))。

一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。

下面是一些用法的示例。

(new Functor(2)).map(function (two) {
  return two + 2;
});
// Functor(4)

(new Functor('flamethrowers')).map(function(s) {
  return s.toUpperCase();
});
// Functor('FLAMETHROWERS')

(new Functor('bombs')).map(_.concat(' away')).map(_.prop('length'));
// Functor(10)

上面的例子说明,函数式编程里面的运算,都是通过函子完成,即运算不直接针对值,而是针对这个值的容器----函子。函子本身具有对外接口(map方法),各种函数就是运算符,通过接口接入容器,引发容器里面的值的变形。

因此,学习函数式编程,实际上就是学习函子的各种运算。 由于可以把运算方法封装在函子里面,所以又衍生出各种不同类型的函子,有多少种运算,就有多少种函子。函数式编程就变成了运用不同的函子,解决实际问题。

四、of 方法

你可能注意到了,上面生成新的函子的时候,用了new命令。这实在太不像函数式编程了,因为new命令是面向对象编程的标志。

函数式编程一般约定,函子有一个of方法,用来生成新的容器。

下面就用of方法替换掉new。

Functor.of = function(val) {
  return new Functor(val);
};

然后,前面的例子就可以改成下面这样。

Functor.of(2).map(function (two) {
  return two + 2;
});
// Functor(4)

这就更像函数式编程了。

五、Maybe 函子

函子接受各种函数,处理容器内部的值。这里就有一个问题,容器内部的值可能是一个空值(比如null),而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错。

Functor.of(null).map(function (s) {
  return s.toUpperCase();
});
// TypeError

上面代码中,函子里面的值是null,结果小写变成大写的时候就出错了。

Maybe 函子就是为了解决这一类问题而设计的。简单说,它的map方法里面设置了空值检查。

class Maybe extends Functor {
  map(f) {
    return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);
  }
}

有了 Maybe 函子,处理空值就不会出错了。

Maybe.of(null).map(function (s) {
  return s.toUpperCase();
});
// Maybe(null)

六、Either 函子

条件运算if...else是最常见的运算之一,函数式编程里面,使用 Either 函子表达。

Either 函子内部有两个值:左值(Left)和右值(Right)。右值是正常情况下使用的值,左值是右值不存在时使用的默认值。

class Either extends Functor {
  constructor(left, right) {
    this.left = left;
    this.right = right;
  }

  map(f) {
    return this.right ? 
      Either.of(this.left, f(this.right)) :
      Either.of(f(this.left), this.right);
  }
}

Either.of = function (left, right) {
  return new Either(left, right);
};

下面是用法。

var addOne = function (x) {
  return x + 1;
};

Either.of(5, 6).map(addOne);
// Either(5, 7);

Either.of(1, null).map(addOne);
// Either(2, null);

上面代码中,如果右值有值,就使用右值,否则使用左值。通过这种方式,Either 函子表达了条件运算。

Either 函子的常见用途是提供默认值。下面是一个例子。

Either
.of({address: 'xxx'}, currentUser.address)
.map(updateField);

上面代码中,如果用户没有提供地址,Either 函子就会使用左值的默认地址。

Either 函子的另一个用途是代替try...catch,使用左值表示错误。

function parseJSON(json) {
  try {
    return Either.of(null, JSON.parse(json));
  } catch (e: Error) {
    return Either.of(e, null);
  }
}

上面代码中,左值为空,就表示没有出错,否则左值会包含一个错误对象e。一般来说,所有可能出错的运算,都可以返回一个 Either 函子。

七、ap 函子

函子里面包含的值,完全可能是函数。我们可以想象这样一种情况,一个函子的值是数值,另一个函子的值是函数。

function addTwo(x) {
  return x + 2;
}

const A = Functor.of(2);
const B = Functor.of(addTwo)

上面代码中,函子A内部的值是2,函子B内部的值是函数addTwo。

有时,我们想让函子B内部的函数,可以使用函子A内部的值进行运算。这时就需要用到 ap 函子。

ap 是 applicative(应用)的缩写。凡是部署了ap方法的函子,就是 ap 函子。

class Ap extends Functor {
  ap(F) {
    return Ap.of(this.val(F.val));
  }
}

注意,ap方法的参数不是函数,而是另一个函子。

因此,前面例子可以写成下面的形式。

Ap.of(addTwo).ap(Function.of(2))
// Ap(4)

ap 函子的意义在于,对于那些多参数的函数,就可以从多个容器之中取值,实现函子的链式操作。

function add(x) {
  return function (y) {
    return x + y;
  };
}

Ap.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));
// Ap(5)

上面代码中,函数add是柯里化以后的形式,一共需要两个参数。通过 ap 函子,我们就可以实现从两个容器之中取值。它还有另外一种写法。

Ap.of(add(2)).ap(Maybe.of(3));

八、Monad 函子

函子是一个容器,可以包含任何值。函子之中再包含一个函子,也是完全合法的。但是,这样就会出现多层嵌套的函子。

Maybe.of(
  Maybe.of(
    Maybe.of({name: 'Mulburry', number: 8402})
  )
)

上面这个函子,一共有三个Maybe嵌套。如果要取出内部的值,就要连续取三次this.val。这当然很不方便,因此就出现了 Monad 函子。

Monad 函子的作用是,总是返回一个单层的函子。 它有一个flatMap方法,与map方法作用相同,唯一的区别是如果生成了一个嵌套函子,它会取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。

class Monad extends Functor {
  join() {
    return this.val;
  }
  flatMap(f) {
    return this.map(f).join();
  }
}

上面代码中,如果函数f返回的是一个函子,那么this.map(f)就会生成一个嵌套的函子。所以,join方法保证了flatMap方法总是返回一个单层的函子。这意味着嵌套的函子会被铺平(flatten)。

九、IO 操作

Monad 函子的重要应用,就是实现 I/O (输入输出)操作。

I/O 是不纯的操作,普通的函数式编程没法做,这时就需要把 IO 操作写成Monad函子,通过它来完成。

var fs = require('fs');

var readFile = function(filename) {
  return new IO(function() {
    return fs.readFileSync(filename, 'utf-8');
  });
};

var print = function(x) {
  return new IO(function() {
    console.log(x);
    return x;
  });
}

上面代码中,读取文件和打印本身都是不纯的操作,但是readFile和print却是纯函数,因为它们总是返回 IO 函子。

如果 IO 函子是一个Monad,具有flatMap方法,那么我们就可以像下面这样调用这两个函数。

readFile('./user.txt')
.flatMap(print)

这就是神奇的地方,上面的代码完成了不纯的操作,但是因为flatMap返回的还是一个 IO 函子,所以这个表达式是纯的。我们通过一个纯的表达式,完成带有副作用的操作,这就是 Monad 的作用。

由于返回还是 IO 函子,所以可以实现链式操作。因此,在大多数库里面,flatMap方法被改名成chain。

var tail = function(x) {
  return new IO(function() {
    return x[x.length - 1];
  });
}

readFile('./user.txt')
.flatMap(tail)
.flatMap(print)

// 等同于
readFile('./user.txt')
.chain(tail)
.chain(print)

上面代码读取了文件user.txt,然后选取最后一行输出。

十、参考链接

(正文完)

=============================================

感谢你读完了全文。下面还有一个推广,请再花一分钟阅读。

去年十月,我介绍了来自硅谷的技术学习平台优达学城(Udacity),他们推出的纳米学位

现在,他们进入中国市场快满周年了,又有一个本地化课程发布了。那就是由 Google 和 Github 合作制作的"前端开发工程师"认证课程。

这个课程完全是国际水准,讲解深入浅出,示例丰富,贴近大公司开发实践,帮助你牢牢掌握那些最实用的前端技术。

课程由硅谷工程师英语讲授,配有全套中文字幕,以及全中文的学习辅导,还有首次引入中国的同步学习小组和导师监督服务,包含一对一的代码辅导。课程通过后,还能拿到 Google、Github 参与颁发的学习认证。

这门课程今天(2月22日)就开始报名了,现在就点击这里,了解更多。我的读者报名时,请使用优惠码ruanyfFEND。

最后,欢迎立即扫码,关注优达学城(微信号:youdaxue),跟踪最新的 IT 在线学习和培训资讯。

(完)

文档信息

]]>
0
<![CDATA[mysql jdbc client端也加了一个maxAllowedPacket参数]]> http://www.udpwork.com/item/16140.html http://www.udpwork.com/item/16140.html#reviews Tue, 21 Feb 2017 23:22:00 +0800 sunchangming http://www.udpwork.com/item/16140.html
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0
<![CDATA[绕过 c api 直接访问 lua 表]]> http://www.udpwork.com/item/16138.html http://www.udpwork.com/item/16138.html#reviews Tue, 21 Feb 2017 14:35:53 +0800 云风 http://www.udpwork.com/item/16138.html 今天试了一下一个想法:绕过 lua 提供的 C API 直接去访问 lua 的表结构,提供在性能及其重要的环境高效访问数据结构的方法。

例如:我们需要在 lua 和 C 中共享一个 vector 3 结构,有两种实现方法:一、把 C struct 实现为 lua 中的 userdata ,然后给 userdata 加上 metatable 以供 lua 中访问内部数据;二、在 lua 中使用一个 table 实现这个 vector3 结构,类似 { x = 0.0 , y = 0.0, z = 0.0 } 这样;然后在 C 里通过 c api (lua_rawget/lua_gettable/lua_getfield) 来访问里面的数据。

前一种方法会导致在 Lua 中访问成本加大、而后一种方法增加的是 C 中访问数据的成本。如果我们只在少数性能敏感的地方通过 C 去操作数据结构,那么第二种方法看起来更简单灵活一些。这样,不需要 C 介入的地方,是没有额外开销的。毕竟、通过 metamethod 索引 userdata 的成本比直接索引一个普通的 table 要重的多。

但是、第二种方法会导致 C 访问数据的成本较大。我们采用 C 代码去处理 vector 数据结构,一定是考虑到性能热点,在语言边界上损失性能感觉不太划算。我觉得或许可以采取一个技巧来加快它。

对于标准的 Lua 实现,构造好的 hash 表,在不添加新 key 的前提下,读写已有的 key ,value 所在的 slot 是不变的。如果我们能记住 slot 的位置,那么就可以绕过 hash 过程、也不需要把 key (这里是一个 string)压栈,直接读写值了。

而且,对于同一个lua_State从一个空表开始,按一致的次序写入相同的 key ,内部数据结构也一定相同。我们可以利用这一点,为同类结构一次性生成索引表。

我写了一小段代码验证我的想法,感觉是可行的:https://gist.github.com/cloudwu/09fca725cb9177d809790b6a7ecdac20

你可以先创建一个 4 个 slot 的 hash 表,key 分别是 x y z__vector。这第 4 个 key__vector是一个标记,表示这是个规整过的数据结构,x y z 都是浮点数,且一定在固定的 slot 里。

void vector_init(lua_State *L, struct vector_offset *vo)可以用来生成 slot 号的结构struct vector_offset。每个lua_State只用生成一次,然后就可以永久保存在 C 的数据结构中。

然后我们用vector_get可以获得内部数据结构 Table * ,这个结构定义在 lobject.h 中,是一个内部 h 文件,这里可以借用一下。之后,就可以用宏 X Y Z 去访问这个 Table * 了。

vector_get中,会检查指定的 table 是否是规整化的 vector 结构,如果不是,就把 x y z 三项读出来,清空 table ,再写回去,并填上__vector标记。此处检查__vector标记是个很轻量的操作。

这个方案适用于 Lua 5.3 ,我没有在老版本的 Lua 上试过,但想必也是可以用的。它的好处是不需要修改任何 Lua 的实现代码、只需要引入 Lua 本身的内部 h 文件即可。所以利用这组 api 实现的 lua 库是可以和其它库兼容共存的。

]]>
今天试了一下一个想法:绕过 lua 提供的 C API 直接去访问 lua 的表结构,提供在性能及其重要的环境高效访问数据结构的方法。

例如:我们需要在 lua 和 C 中共享一个 vector 3 结构,有两种实现方法:一、把 C struct 实现为 lua 中的 userdata ,然后给 userdata 加上 metatable 以供 lua 中访问内部数据;二、在 lua 中使用一个 table 实现这个 vector3 结构,类似 { x = 0.0 , y = 0.0, z = 0.0 } 这样;然后在 C 里通过 c api (lua_rawget/lua_gettable/lua_getfield) 来访问里面的数据。

前一种方法会导致在 Lua 中访问成本加大、而后一种方法增加的是 C 中访问数据的成本。如果我们只在少数性能敏感的地方通过 C 去操作数据结构,那么第二种方法看起来更简单灵活一些。这样,不需要 C 介入的地方,是没有额外开销的。毕竟、通过 metamethod 索引 userdata 的成本比直接索引一个普通的 table 要重的多。

但是、第二种方法会导致 C 访问数据的成本较大。我们采用 C 代码去处理 vector 数据结构,一定是考虑到性能热点,在语言边界上损失性能感觉不太划算。我觉得或许可以采取一个技巧来加快它。

对于标准的 Lua 实现,构造好的 hash 表,在不添加新 key 的前提下,读写已有的 key ,value 所在的 slot 是不变的。如果我们能记住 slot 的位置,那么就可以绕过 hash 过程、也不需要把 key (这里是一个 string)压栈,直接读写值了。

而且,对于同一个lua_State从一个空表开始,按一致的次序写入相同的 key ,内部数据结构也一定相同。我们可以利用这一点,为同类结构一次性生成索引表。

我写了一小段代码验证我的想法,感觉是可行的:https://gist.github.com/cloudwu/09fca725cb9177d809790b6a7ecdac20

你可以先创建一个 4 个 slot 的 hash 表,key 分别是 x y z__vector。这第 4 个 key__vector是一个标记,表示这是个规整过的数据结构,x y z 都是浮点数,且一定在固定的 slot 里。

void vector_init(lua_State *L, struct vector_offset *vo)可以用来生成 slot 号的结构struct vector_offset。每个lua_State只用生成一次,然后就可以永久保存在 C 的数据结构中。

然后我们用vector_get可以获得内部数据结构 Table * ,这个结构定义在 lobject.h 中,是一个内部 h 文件,这里可以借用一下。之后,就可以用宏 X Y Z 去访问这个 Table * 了。

vector_get中,会检查指定的 table 是否是规整化的 vector 结构,如果不是,就把 x y z 三项读出来,清空 table ,再写回去,并填上__vector标记。此处检查__vector标记是个很轻量的操作。

这个方案适用于 Lua 5.3 ,我没有在老版本的 Lua 上试过,但想必也是可以用的。它的好处是不需要修改任何 Lua 的实现代码、只需要引入 Lua 本身的内部 h 文件即可。所以利用这组 api 实现的 lua 库是可以和其它库兼容共存的。

]]>
0
<![CDATA[tensorflow alex performance test]]> http://www.udpwork.com/item/16137.html http://www.udpwork.com/item/16137.html#reviews Mon, 20 Feb 2017 23:19:00 +0800 sunchangming http://www.udpwork.com/item/16137.html
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0
<![CDATA[独立思考的重要性]]> http://www.udpwork.com/item/16136.html http://www.udpwork.com/item/16136.html#reviews Sun, 19 Feb 2017 23:23:28 +0800 唐巧 http://www.udpwork.com/item/16136.html 罗辑思维的「逻辑」问题

我有一段时间很不喜欢罗辑思维,因为我发现他为了证明一个观点,会举一些非常不恰当的例子。而很多例子是可以从多方面来解读的,就像同样一本圣经,却可以成为多个宗教的教义一样。

昨天在微博上看到 @一乐 发了一篇微博

(产品设计)是一个擅长辩论的行业,而在那些雾里看花也没有直观感受的领域,怎么说都有道理,谁都别想说服谁。

一乐很恰当地说明了这种尴尬的现象。当我们作为程序员写代码的时候,一切都是确定的。但是在非编程领域,包括商业思考,产品设计,UI 设计,营销思路,竞争思路,很难有绝对的对错。

讲故事时必须有明确的观点

刚刚说我不喜欢罗辑思维拿一些故事来生硬地证明他的观点。但是,我又还是继续在听罗辑思维。为什么呢?因为当我们在讲故事或者阐述观点的时候,我们必须给听众一个确定的内容,这个确定的内容就是一个明确的观点。

我还记得我发表过一篇《成长为 iOS 大 V 的秘密》的文章。一位读者看完后回复得非常切中要害,他说我在文章中故意弱化了我的天赋,而强化了努力和坚持的效果。

我觉得他说得非常对。我在文章中说我毕业于一个计算机排名全国 100 名以外的学校,但是我也可以说我毕业于一个综合排名前 10 的 985 学校,并且我在这个学校的计算机专业成绩排名优秀并保送该校研究生。我可以说我自学 iOS 成才,但是也可以说我在网易时,可以下载有道词典和有道笔记的 iOS 代码资源学习,以及有大量优秀的同事可以帮助我解答自学中的疑问。

那么,我如果这么写文章,大家看了会怎么想呢?文章到底表达了什么观点呢?我其实觉得努力,机遇,天赋都很重要,但是如果要说到底什么是最重要的,我还是觉得是努力。所以我弱化了我的天赋和机遇,强化了努力的作用,以便传达出文章的核心思想。

罗辑思维也是这样,不管他讲什么例子,他都是在用故事试图证明他的观点。也许故事并不太恰当,但是观点本身其实就不是绝对正确的。所以只要把罗胖的观点放在地上思考这个观点的可取之处就可以了,危险的是那些把他的观点捧着举着的人。

这就是为什么张小龙会说「我说的都是错的」,小道消息的冯大辉也会说 「希望文章能给大家启发就好」。时代在变,我们不缺观点,缺的是一种辩证看问题,独立思考的态度。

如何独立思考

要独立思考,首先要学会吸收不同的观点。在这方面,多读书是最好的方式。书读多了,你自然就会发现不同的书中的观点会有冲突,这就给了你思考的基础。有了这个,你再去思考两种观点最最基本的假设,检查逻辑推导的过程,这样就容易找到对的一方了。

一旦你多次进行独立思考,你就会有意识地「怀疑」一些观点,然后利用自己的已有知识或者进行适当地网上搜索学习,你就可以验证自己的怀疑是否正确,从进培养起自己独立思考的能力。

如果打个比方,那么独立思考就是把别人嚼碎的食物还原,自己嚼一遍,因为只有自己嚼一遍,才能知道口感是不是真的好。

例子

我们选罗辑思维第 200 期《古典:超级个体》中的一个观点来挑战一下。这一期不是罗胖主持的,按理说应该挑罗胖自己讲的内容,但是我确实没有动机专门挑他的毛病,所以只能凭印象找最近的一期。这期节目中讲到,人以后的寿命变长,会使得婚姻很容易破裂。而他的理由是:夫妻之间婚姻的稳固,大多数是由于年轻的时候需要共同抚养孩子,年老之后不再好重新找伴侣只好将就过。

但是我正好看了《少有人走的路》这一本书,这里面提到了正确的婚姻价值观。所以,这一期节目中的这个例子,在很多家庭中应该是对的,但是这仅表示这些家庭成员缺少正确的对待婚姻的态度。有了这样的思考,我其实看待这个观点就更加成熟了。

我们生活中类似的需要独立思考的观点还有很多,大家都可以想一下,比如:

  • 今日头条真的只是一个「新闻客户端」吗?
  • 为什么很多大公司继续使用 Objective-C,不用 Swift?

这个时代人云亦云最可怕了,希望大家都能够有独立思考的能力。

与君共勉。

]]>
罗辑思维的「逻辑」问题

我有一段时间很不喜欢罗辑思维,因为我发现他为了证明一个观点,会举一些非常不恰当的例子。而很多例子是可以从多方面来解读的,就像同样一本圣经,却可以成为多个宗教的教义一样。

昨天在微博上看到 @一乐 发了一篇微博

(产品设计)是一个擅长辩论的行业,而在那些雾里看花也没有直观感受的领域,怎么说都有道理,谁都别想说服谁。

一乐很恰当地说明了这种尴尬的现象。当我们作为程序员写代码的时候,一切都是确定的。但是在非编程领域,包括商业思考,产品设计,UI 设计,营销思路,竞争思路,很难有绝对的对错。

讲故事时必须有明确的观点

刚刚说我不喜欢罗辑思维拿一些故事来生硬地证明他的观点。但是,我又还是继续在听罗辑思维。为什么呢?因为当我们在讲故事或者阐述观点的时候,我们必须给听众一个确定的内容,这个确定的内容就是一个明确的观点。

我还记得我发表过一篇《成长为 iOS 大 V 的秘密》的文章。一位读者看完后回复得非常切中要害,他说我在文章中故意弱化了我的天赋,而强化了努力和坚持的效果。

我觉得他说得非常对。我在文章中说我毕业于一个计算机排名全国 100 名以外的学校,但是我也可以说我毕业于一个综合排名前 10 的 985 学校,并且我在这个学校的计算机专业成绩排名优秀并保送该校研究生。我可以说我自学 iOS 成才,但是也可以说我在网易时,可以下载有道词典和有道笔记的 iOS 代码资源学习,以及有大量优秀的同事可以帮助我解答自学中的疑问。

那么,我如果这么写文章,大家看了会怎么想呢?文章到底表达了什么观点呢?我其实觉得努力,机遇,天赋都很重要,但是如果要说到底什么是最重要的,我还是觉得是努力。所以我弱化了我的天赋和机遇,强化了努力的作用,以便传达出文章的核心思想。

罗辑思维也是这样,不管他讲什么例子,他都是在用故事试图证明他的观点。也许故事并不太恰当,但是观点本身其实就不是绝对正确的。所以只要把罗胖的观点放在地上思考这个观点的可取之处就可以了,危险的是那些把他的观点捧着举着的人。

这就是为什么张小龙会说「我说的都是错的」,小道消息的冯大辉也会说 「希望文章能给大家启发就好」。时代在变,我们不缺观点,缺的是一种辩证看问题,独立思考的态度。

如何独立思考

要独立思考,首先要学会吸收不同的观点。在这方面,多读书是最好的方式。书读多了,你自然就会发现不同的书中的观点会有冲突,这就给了你思考的基础。有了这个,你再去思考两种观点最最基本的假设,检查逻辑推导的过程,这样就容易找到对的一方了。

一旦你多次进行独立思考,你就会有意识地「怀疑」一些观点,然后利用自己的已有知识或者进行适当地网上搜索学习,你就可以验证自己的怀疑是否正确,从进培养起自己独立思考的能力。

如果打个比方,那么独立思考就是把别人嚼碎的食物还原,自己嚼一遍,因为只有自己嚼一遍,才能知道口感是不是真的好。

例子

我们选罗辑思维第 200 期《古典:超级个体》中的一个观点来挑战一下。这一期不是罗胖主持的,按理说应该挑罗胖自己讲的内容,但是我确实没有动机专门挑他的毛病,所以只能凭印象找最近的一期。这期节目中讲到,人以后的寿命变长,会使得婚姻很容易破裂。而他的理由是:夫妻之间婚姻的稳固,大多数是由于年轻的时候需要共同抚养孩子,年老之后不再好重新找伴侣只好将就过。

但是我正好看了《少有人走的路》这一本书,这里面提到了正确的婚姻价值观。所以,这一期节目中的这个例子,在很多家庭中应该是对的,但是这仅表示这些家庭成员缺少正确的对待婚姻的态度。有了这样的思考,我其实看待这个观点就更加成熟了。

我们生活中类似的需要独立思考的观点还有很多,大家都可以想一下,比如:

  • 今日头条真的只是一个「新闻客户端」吗?
  • 为什么很多大公司继续使用 Objective-C,不用 Swift?

这个时代人云亦云最可怕了,希望大家都能够有独立思考的能力。

与君共勉。

]]>
0
<![CDATA[docker 1.13默认会在iptables里drop forward包]]> http://www.udpwork.com/item/16135.html http://www.udpwork.com/item/16135.html#reviews Sun, 19 Feb 2017 23:04:00 +0800 sunchangming http://www.udpwork.com/item/16135.html
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0
<![CDATA[一个关于安卓打包提速的小改进]]> http://www.udpwork.com/item/16134.html http://www.udpwork.com/item/16134.html#reviews Sun, 19 Feb 2017 20:19:00 +0800 技术小黑屋 http://www.udpwork.com/item/16134.html 作为App开发者,打包和发包是经常要进行的工作。鉴于国内的特殊情况,造就了不可胜举的应用市场。为了便于跟踪统计必要的数据信息,我们通常会针对每个市场都进行打包。这些包总的来说几乎是一致的,可能唯一的的差别就是渠道号信息不一样。

Flipboard中国版本目前覆盖了大概10几个渠道,目前唯一不同的就是渠道号信息。

最早的实现方式为

1
buildConfigField "String", "CHANNEL_ID", null == versionProps['CHANNEL_ID'] ? /""/ : '"' + versionProps['CHANNEL_ID'] + '"'

上述实现的缺点有

  • 无法在manifest中配置包含渠道号信息的meta数据
  • 会导致每次重新编译代码,以及后续的多次Proguard优化,相对很耗时。打包时间将近4分钟

后来改进的方式为

1
resValue "string", "channel_id", null == versionProps['CHANNEL_ID'] ? /""/ : '"' + versionProps['CHANNEL_ID'] + '"'

新的实现解决了上面的两个问题

  • 可以在manifest中实现配置渠道号信息
  • 无需重新编译源码,也无需后续的proguard的代码优化。只更新resource资源即可。打包时间缩短到14秒左右。

经过如此一个小改动,从此我们不再需要漫长的等待和浪费机器性能。

]]>
作为App开发者,打包和发包是经常要进行的工作。鉴于国内的特殊情况,造就了不可胜举的应用市场。为了便于跟踪统计必要的数据信息,我们通常会针对每个市场都进行打包。这些包总的来说几乎是一致的,可能唯一的的差别就是渠道号信息不一样。

Flipboard中国版本目前覆盖了大概10几个渠道,目前唯一不同的就是渠道号信息。

最早的实现方式为

1
buildConfigField "String", "CHANNEL_ID", null == versionProps['CHANNEL_ID'] ? /""/ : '"' + versionProps['CHANNEL_ID'] + '"'

上述实现的缺点有

  • 无法在manifest中配置包含渠道号信息的meta数据
  • 会导致每次重新编译代码,以及后续的多次Proguard优化,相对很耗时。打包时间将近4分钟

后来改进的方式为

1
resValue "string", "channel_id", null == versionProps['CHANNEL_ID'] ? /""/ : '"' + versionProps['CHANNEL_ID'] + '"'

新的实现解决了上面的两个问题

  • 可以在manifest中实现配置渠道号信息
  • 无需重新编译源码,也无需后续的proguard的代码优化。只更新resource资源即可。打包时间缩短到14秒左右。

经过如此一个小改动,从此我们不再需要漫长的等待和浪费机器性能。

]]>
0
<![CDATA[tensorflow distributed runtime初窥]]> http://www.udpwork.com/item/16126.html http://www.udpwork.com/item/16126.html#reviews Sat, 18 Feb 2017 22:58:00 +0800 sunchangming http://www.udpwork.com/item/16126.html
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0
<![CDATA[为什么 Windows 的文件系统会有盘符,使用反斜杠分割路径]]> http://www.udpwork.com/item/16123.html http://www.udpwork.com/item/16123.html#reviews Sat, 18 Feb 2017 01:12:46 +0800 云风 http://www.udpwork.com/item/16123.html 今天同事在公司群里贴了张屏幕截图,上面有30+ 个盘。从 C: 排到 Z: , 然后还有 CC: CD: ,调侃问 Windows 能管理多少个盘。

图应该是 P 出来搞笑的,除去 A B 盘保留给已经淘汰的软驱用外,windows 超过 Z 盘后就不在能增加了。如果有更多储存设备,则需要用把设备挂接在空目录上(ntfs 支持)。

为什么 Windows 会有盘符这个诡异的东西呢?

按如非必要、勿增实体的原则,只用路径就够了呀。物理分区完全可以隐藏在文件系统之后,在 Unix 系的操作系统中,分区是用挂接点的方式挂接在虚拟文件系统中的(ntfs 其实也支持)。如果是为了方便记忆和保持用户习惯,完全可以把分区顺着挂接到 /c /d /e /f 下。如果你在 windows 下安装 mingw/msys 它就是这样处理 C 盘、D 盘 …… 的。

答案要从历史中找。

Windows 的前身是 微软的 Dos 系统。我见过的最古老的 MS Dos 是在我同学家的一台旧 IBM PC/XT 上。10M 的硬盘、只有 256K 内存,配置的系统是 PC(MS) Dos 2.0 。

知道这个 2.0 版的 Dos 比它的前身最大的改进是什么吗?它比 MS DOS 1.0 多支持了硬盘,以及层级目录结构。

同时期的主流电脑是 Apple ][ ,它的原生操作系统(Apple Dos)是不支持硬盘的,软盘上只有一个根目录。MS Dos 1.0 也一样。

除了 IBM PC,Apple ][ ,那个年代个人电脑品牌其实非常多。比如我的第一台电脑就是港产的 Z80 机器(laser 310 )。当时 8 位机上最流行的个人操作系统是一个叫做 CP/M 的系统。不过当时流行直接用汇编写程序,这个 CP/M 必须跑在 Z80(8080) 的指令集上。当年 Apple ][ 上流行过一种叫做 Z80 卡的扩展件,就是为了可以跑 CP/M 系统。

微软起家为 IBM 的 8086 系列 PC 写操作系统时,就借鉴了 CP/M 的一些东西,其中就有盘符这个东西。在没有硬盘,及内存小于软盘容量的年代,配置两个软盘驱动器是最方便的,所以就有了 A B 两个盘符(方便数据对拷)。每个文件都可以写成 A:FILENAME.EXT 的形式。这就是盘符的由来。

btw, 早年 IBM 想和 CP/M 合作没谈成,后来 PC 流行后,CP/M 又反回来兼容了 MS-DOS 跑在 PC 系统上,改名字叫 DR-DOS ,应该很多同学有印象。

MS DOS 发展到 2.0 时,由于 IBM 给 PC/XT 增加了 10M 的硬盘,所以盘符就被扩展到了 C: 表示硬盘。储存空间的增加导致了必须增加文件目录结构。可为啥微软选择了反斜杠,而不是 Unix 系列中已经很广泛的 / 呢?

这是因为,MS Dos 已有的很多命令行工具。当时微软做开发的人有 DEC 的背景,DEC 的操作系统上是用 / 做命令行参数分割符,而不是 Unix 系列用的 - ,就这样沿用到了 MS Dos 里。btw, 我读大学时第一次接触 Linux ,感觉输入最不习惯的就是用 - 而不是 / 。

为了防止混淆,目录风格符就不能再使用 / 了,DEC 系统中用的是点 "." ,可 MS Dos 学了 CP/M 用 . 做文件后缀名分割,所以就用了让后代程序员深恶痛绝的反斜杠 \ 。

ps. C 系列的语言中,\ 是字符串转义符,写在字符串里你需要写 \ ,特别麻烦。

今天发现本 Blog 用的虚拟主机 linode 降价了,现在只要 5$ 一个月。去后台调一下即可。Linode 用 7年了,感觉非常好。

如果有同学也有兴趣租一个境外的 vps ,可以点这个链接,或者填我的 referral code : 538bab39bc1265a2ce54115d1f86e2bc81e4d133 。

]]>
今天同事在公司群里贴了张屏幕截图,上面有30+ 个盘。从 C: 排到 Z: , 然后还有 CC: CD: ,调侃问 Windows 能管理多少个盘。

图应该是 P 出来搞笑的,除去 A B 盘保留给已经淘汰的软驱用外,windows 超过 Z 盘后就不在能增加了。如果有更多储存设备,则需要用把设备挂接在空目录上(ntfs 支持)。

为什么 Windows 会有盘符这个诡异的东西呢?

按如非必要、勿增实体的原则,只用路径就够了呀。物理分区完全可以隐藏在文件系统之后,在 Unix 系的操作系统中,分区是用挂接点的方式挂接在虚拟文件系统中的(ntfs 其实也支持)。如果是为了方便记忆和保持用户习惯,完全可以把分区顺着挂接到 /c /d /e /f 下。如果你在 windows 下安装 mingw/msys 它就是这样处理 C 盘、D 盘 …… 的。

答案要从历史中找。

Windows 的前身是 微软的 Dos 系统。我见过的最古老的 MS Dos 是在我同学家的一台旧 IBM PC/XT 上。10M 的硬盘、只有 256K 内存,配置的系统是 PC(MS) Dos 2.0 。

知道这个 2.0 版的 Dos 比它的前身最大的改进是什么吗?它比 MS DOS 1.0 多支持了硬盘,以及层级目录结构。

同时期的主流电脑是 Apple ][ ,它的原生操作系统(Apple Dos)是不支持硬盘的,软盘上只有一个根目录。MS Dos 1.0 也一样。

除了 IBM PC,Apple ][ ,那个年代个人电脑品牌其实非常多。比如我的第一台电脑就是港产的 Z80 机器(laser 310 )。当时 8 位机上最流行的个人操作系统是一个叫做 CP/M 的系统。不过当时流行直接用汇编写程序,这个 CP/M 必须跑在 Z80(8080) 的指令集上。当年 Apple ][ 上流行过一种叫做 Z80 卡的扩展件,就是为了可以跑 CP/M 系统。

微软起家为 IBM 的 8086 系列 PC 写操作系统时,就借鉴了 CP/M 的一些东西,其中就有盘符这个东西。在没有硬盘,及内存小于软盘容量的年代,配置两个软盘驱动器是最方便的,所以就有了 A B 两个盘符(方便数据对拷)。每个文件都可以写成 A:FILENAME.EXT 的形式。这就是盘符的由来。

btw, 早年 IBM 想和 CP/M 合作没谈成,后来 PC 流行后,CP/M 又反回来兼容了 MS-DOS 跑在 PC 系统上,改名字叫 DR-DOS ,应该很多同学有印象。

MS DOS 发展到 2.0 时,由于 IBM 给 PC/XT 增加了 10M 的硬盘,所以盘符就被扩展到了 C: 表示硬盘。储存空间的增加导致了必须增加文件目录结构。可为啥微软选择了反斜杠,而不是 Unix 系列中已经很广泛的 / 呢?

这是因为,MS Dos 已有的很多命令行工具。当时微软做开发的人有 DEC 的背景,DEC 的操作系统上是用 / 做命令行参数分割符,而不是 Unix 系列用的 - ,就这样沿用到了 MS Dos 里。btw, 我读大学时第一次接触 Linux ,感觉输入最不习惯的就是用 - 而不是 / 。

为了防止混淆,目录风格符就不能再使用 / 了,DEC 系统中用的是点 "." ,可 MS Dos 学了 CP/M 用 . 做文件后缀名分割,所以就用了让后代程序员深恶痛绝的反斜杠 \ 。

ps. C 系列的语言中,\ 是字符串转义符,写在字符串里你需要写 \ ,特别麻烦。

今天发现本 Blog 用的虚拟主机 linode 降价了,现在只要 5$ 一个月。去后台调一下即可。Linode 用 7年了,感觉非常好。

如果有同学也有兴趣租一个境外的 vps ,可以点这个链接,或者填我的 referral code : 538bab39bc1265a2ce54115d1f86e2bc81e4d133 。

]]>
0
<![CDATA[gtl是个有意思的东西]]> http://www.udpwork.com/item/16125.html http://www.udpwork.com/item/16125.html#reviews Fri, 17 Feb 2017 21:43:00 +0800 sunchangming http://www.udpwork.com/item/16125.html
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0
<![CDATA[知识电商?不妨先看看已小成气候的付费音频]]> http://www.udpwork.com/item/16124.html http://www.udpwork.com/item/16124.html#reviews Fri, 17 Feb 2017 17:12:53 +0800 魏武挥 http://www.udpwork.com/item/16124.html 《知识电商这个市场会有多大》一文中,我提到了去年略有规模的四个内容付费平台,其中之一就是喜马拉雅。由于其地处上海,我略知一二,就可以从这个平台上的付费音频中切入,来管窥一二。

本文所提及的一些信息,我自有信源,但我不想提供出处,请周知与见谅。

 

到目前为止,整个平台上,付费订阅源尚未过万,相对于整个平台来说,比例不高,可能不到1%。但去年123知识狂欢节所形成的数千万流水(官方宣称5088万),给了平台方很大的信心。平台甚至立下了2017年一个相当夸张的付费金额目标。

平台目前实施的分成方式是:五五。早期使用邀请制,现在则进入申请制。但凡有兴趣做付费音频的,都可以注册提交申请。审核方面还是比较严的,主要和预期能否卖好有关,后述。

平台上节目主要有两种,日播型和周播型,前者一般售价199元/年(相当于一个微信红包),后者则为99元/年。平台用喜币这个虚拟货币,其实与人民币就是1比1。

喜马拉雅平台上根据主播自身的情况,有两种人。

其一是所谓自带流量效应的名人,比如马东团队,这可能是喜马拉雅付费板块当下的头牌,根据APP里显示的数据,当下已经播放3087万次。再比如吴晓波团队,目前播放533万次,不过“每天听见吴晓波”这个节目并非独家,吴晓波频道自己的平台上也有播出。而“马东携奇葩天团亲授好好说话”这个节目,为喜马拉雅独播产品。

另外则是所谓的素人,也就是大众层面上并没有什么太大名气,以及本身也非从事主播工作的内容制作人。喜马拉雅上有三个典型案例:田艺苗的“古典音乐很难吗?”,555万次播放;徐洁的“声音教练:如何练就好声音”,585万次播放;陈见的“如何释放性魅力”,106万次播放。

这三个人有个共同的特征:本来就是教师型人物。

田艺苗的确是教师,上海音乐学院副教授,讲古典音乐正是她本人专长。徐洁原来是个空姐,由于空姐本身就需要训练如何和乘客交流,她自己也兼差给其它空姐培训,现在已专职在做付费音频;陈见是留美博士,有美国性学哲学博士和主持临床性学家及国内相关领域诸多头衔,是这个领域的专家。

根据经验,其实是素人更容易培养。

 

样音。

这是一档付费音频的重要一环。

无论是前期邀请制所邀请来的主播,还是当下自行注册申请的主播,都需要制作样音。平台运营方会根据样音,提出修改意见。

以马东团队为例。

一开始,马东团队期望每期节目以某个故事为开场,比如说,我有一个朋友叫张三,昨天打算去和他老板提出辞职,当时他的情况是,吧啦吧啦。但平台运营方听过样音后,建议他从场景入手,直奔主题:如果你今天要向老板提出辞职,你该如何启口?

这样的建议,其实和跳出率有关。我以前写过一篇题为《网络音频,爆发期的前夜》的文章,引用过喜马拉雅平台的一些数据,大多数节目的跳出率都在开始的1/4时间段。网络音频节目绕圈子说话,是行不通的。

样音这个过程会反复打磨,在样音阶段就流产的付费音频项目相当多,最极端的一个例子是,样音打磨了半年的某主播,最终该项目流产。

当双方都觉得这个样音有销售把握时,节目上线。运营首月,平台会比较密切地监控该节目的销售水平以及收听水平(比如跳出率),不断提出意见。一个月后,节目趋于成熟,平台不再投入大量精力,由主播自行发展。

但平台不是撒手不管,当它发现运营数据出现疲态时,会向主播提出建议,一般就是建议搞活动或举办线下见面会。

 

供给者。

什么人更容易售卖他们的音频内容?

首先,样音的存在及其重要性,使得平台方经过一段时间运营后发现,素人更容易售卖内容。因为素人通常会比名人更容易接受整改意见。而音频制作,的确是相当多人乃至名人以前从来没碰过的。

作为一个名人,马东及其团队还是相当配合,仔细打磨,但更多的名人,未必放得下这个架子。有流量的名人,在喜马拉雅平台上,其音频未必就一定卖得好。

这也是为什么我虽然动过脑子要做个付费音频节目后来放弃的原因。我本人并不耐烦反复修改——这从我的文字中经常有错别字可以看出来。反复打磨一个东西,是我完全不擅长的。

其次,这个素人要在一个领域中比较专业,ta能输出一套成体系的东西,而不是今天东扯一个段落明天西说一段。喜马拉雅把几乎所有的付费内容都称之为“课程”。

就在昨天,有消息说,腾讯将着手在微信中部署付费阅读。有可能那些获得赞赏成百上千次数的文章,真不一定适合前置付费。课程感、体系感,对于付费与否的意义很大——我这里并不是说就真的是学校课程、成体系的基础知识教育。

可以这么说,赞得好未必卖得好,有时候甚至是有可能冲突的。

 

需求者,及,消费时间段。

付费音频的主要消费者是85-90后,这其实并非是喜马拉雅过去的主流人群。

消费时间段:上下班通勤时间,以及,晚饭后睡觉前。

上下班通勤时间比较容易理解,这是典型的伴随式内容消费。

晚饭后睡觉前,则有可能是伴随式,比如吃完饭做做家务,或者散散步。当然也有可能是大段时间用于集中注意力进行学习。

85-90后的年轻人群可能更愿意接受“收费”这种模式,这和他们出生于富足时代有关。

但为什么不是80-85后?

这就是从什么样的内容受欢迎倒推得出,也和我所掌握信源正好匹配。

 

内容。

一般意义上,付费音频内容需要聚焦再聚焦,因为是课程式,故而需要成体系。

喜马拉雅在过去,亲子节目非常受欢迎。

比如说,并非排在首位但也在排行榜靠前的“晚安妈妈睡前故事”有4700万次播放,而相对同样并非首位但排行榜靠前的“孩子耳边的科学家“只有16.5万次的播放。

这并不是纯粹因为免费和收费所引起的落差。

同样属于商业分类里的,秦朔朋友圈的播放量是3500万次,但收费的孙宇晨财富自由革命之路也有536万次播放之多。

说起孙宇晨这档栏目,标签是90后亿万富翁,马云湖畔大学唯一90后学员,福布斯2015年中国30位30岁以下创业者等,年龄的强调非常明显。

通常来说,85前有孩子的概率超过了85后。

至于头牌栏目马东的奇葩团队,也是主讲个人成长、职场技巧等。而90后,早已踏上职场舞台。

情感类的内容卖得不是太出色,这可能和这类内容免费的极多有关,而陈见的女性性心理学,和年轻人相对更愿意主宰自己的性幸福,以及相关内容本身很稀缺有关。

 

另外一种内容。

田艺苗的古典音乐教学大卖,我一开始是很惊讶的。

我同样一开始也惊讶于这两日的中国古诗词大会的热议。

不过思考了一会儿,我开始理解这件事。

说得糙一点,这叫“装逼需求推动的内容付费”。

今天这个时代的年轻人,对精神上的追求并不低,甚至超过他们的70后前辈。道理就在于他们生长在富足经济下。不过恰恰可惜的是,在应试教育的指挥棒下,类似古典音乐、诗词赏析,并不是学校教育的主流。

这就非常能说通为什么田艺苗的古典音乐能大卖,刚刚上线的蒙曼品最美唐诗能迅速成为爆款了。

 

小结。

1、内容做成课程式,体系化,可能更容易售卖,需要聚焦聚焦再聚焦,并试运营进行调整;

2、85-90后是消费主体,内容应考虑他们的主要需求;

3、制作人本身是否有名不重要,重要的是能成体系输出,并与平台能充分合作;

4、部分学校不教授但与品位表达有关的素养教育,依然能大卖;

5、以上虽然是音频领域,但对于图文、视频,可能同样具有参考价值。

 

—— 首发 扯氮集 ——

版权声明 及 商业合作

作者执教于上海交通大学媒体与设计学院,天奇阿米巴创投基金投资合伙人

知识电商?不妨先看看已小成气候的付费音频,首发于扯氮集

]]>
《知识电商这个市场会有多大》一文中,我提到了去年略有规模的四个内容付费平台,其中之一就是喜马拉雅。由于其地处上海,我略知一二,就可以从这个平台上的付费音频中切入,来管窥一二。

本文所提及的一些信息,我自有信源,但我不想提供出处,请周知与见谅。

 

到目前为止,整个平台上,付费订阅源尚未过万,相对于整个平台来说,比例不高,可能不到1%。但去年123知识狂欢节所形成的数千万流水(官方宣称5088万),给了平台方很大的信心。平台甚至立下了2017年一个相当夸张的付费金额目标。

平台目前实施的分成方式是:五五。早期使用邀请制,现在则进入申请制。但凡有兴趣做付费音频的,都可以注册提交申请。审核方面还是比较严的,主要和预期能否卖好有关,后述。

平台上节目主要有两种,日播型和周播型,前者一般售价199元/年(相当于一个微信红包),后者则为99元/年。平台用喜币这个虚拟货币,其实与人民币就是1比1。

喜马拉雅平台上根据主播自身的情况,有两种人。

其一是所谓自带流量效应的名人,比如马东团队,这可能是喜马拉雅付费板块当下的头牌,根据APP里显示的数据,当下已经播放3087万次。再比如吴晓波团队,目前播放533万次,不过“每天听见吴晓波”这个节目并非独家,吴晓波频道自己的平台上也有播出。而“马东携奇葩天团亲授好好说话”这个节目,为喜马拉雅独播产品。

另外则是所谓的素人,也就是大众层面上并没有什么太大名气,以及本身也非从事主播工作的内容制作人。喜马拉雅上有三个典型案例:田艺苗的“古典音乐很难吗?”,555万次播放;徐洁的“声音教练:如何练就好声音”,585万次播放;陈见的“如何释放性魅力”,106万次播放。

这三个人有个共同的特征:本来就是教师型人物。

田艺苗的确是教师,上海音乐学院副教授,讲古典音乐正是她本人专长。徐洁原来是个空姐,由于空姐本身就需要训练如何和乘客交流,她自己也兼差给其它空姐培训,现在已专职在做付费音频;陈见是留美博士,有美国性学哲学博士和主持临床性学家及国内相关领域诸多头衔,是这个领域的专家。

根据经验,其实是素人更容易培养。

 

样音。

这是一档付费音频的重要一环。

无论是前期邀请制所邀请来的主播,还是当下自行注册申请的主播,都需要制作样音。平台运营方会根据样音,提出修改意见。

以马东团队为例。

一开始,马东团队期望每期节目以某个故事为开场,比如说,我有一个朋友叫张三,昨天打算去和他老板提出辞职,当时他的情况是,吧啦吧啦。但平台运营方听过样音后,建议他从场景入手,直奔主题:如果你今天要向老板提出辞职,你该如何启口?

这样的建议,其实和跳出率有关。我以前写过一篇题为《网络音频,爆发期的前夜》的文章,引用过喜马拉雅平台的一些数据,大多数节目的跳出率都在开始的1/4时间段。网络音频节目绕圈子说话,是行不通的。

样音这个过程会反复打磨,在样音阶段就流产的付费音频项目相当多,最极端的一个例子是,样音打磨了半年的某主播,最终该项目流产。

当双方都觉得这个样音有销售把握时,节目上线。运营首月,平台会比较密切地监控该节目的销售水平以及收听水平(比如跳出率),不断提出意见。一个月后,节目趋于成熟,平台不再投入大量精力,由主播自行发展。

但平台不是撒手不管,当它发现运营数据出现疲态时,会向主播提出建议,一般就是建议搞活动或举办线下见面会。

 

供给者。

什么人更容易售卖他们的音频内容?

首先,样音的存在及其重要性,使得平台方经过一段时间运营后发现,素人更容易售卖内容。因为素人通常会比名人更容易接受整改意见。而音频制作,的确是相当多人乃至名人以前从来没碰过的。

作为一个名人,马东及其团队还是相当配合,仔细打磨,但更多的名人,未必放得下这个架子。有流量的名人,在喜马拉雅平台上,其音频未必就一定卖得好。

这也是为什么我虽然动过脑子要做个付费音频节目后来放弃的原因。我本人并不耐烦反复修改——这从我的文字中经常有错别字可以看出来。反复打磨一个东西,是我完全不擅长的。

其次,这个素人要在一个领域中比较专业,ta能输出一套成体系的东西,而不是今天东扯一个段落明天西说一段。喜马拉雅把几乎所有的付费内容都称之为“课程”。

就在昨天,有消息说,腾讯将着手在微信中部署付费阅读。有可能那些获得赞赏成百上千次数的文章,真不一定适合前置付费。课程感、体系感,对于付费与否的意义很大——我这里并不是说就真的是学校课程、成体系的基础知识教育。

可以这么说,赞得好未必卖得好,有时候甚至是有可能冲突的。

 

需求者,及,消费时间段。

付费音频的主要消费者是85-90后,这其实并非是喜马拉雅过去的主流人群。

消费时间段:上下班通勤时间,以及,晚饭后睡觉前。

上下班通勤时间比较容易理解,这是典型的伴随式内容消费。

晚饭后睡觉前,则有可能是伴随式,比如吃完饭做做家务,或者散散步。当然也有可能是大段时间用于集中注意力进行学习。

85-90后的年轻人群可能更愿意接受“收费”这种模式,这和他们出生于富足时代有关。

但为什么不是80-85后?

这就是从什么样的内容受欢迎倒推得出,也和我所掌握信源正好匹配。

 

内容。

一般意义上,付费音频内容需要聚焦再聚焦,因为是课程式,故而需要成体系。

喜马拉雅在过去,亲子节目非常受欢迎。

比如说,并非排在首位但也在排行榜靠前的“晚安妈妈睡前故事”有4700万次播放,而相对同样并非首位但排行榜靠前的“孩子耳边的科学家“只有16.5万次的播放。

这并不是纯粹因为免费和收费所引起的落差。

同样属于商业分类里的,秦朔朋友圈的播放量是3500万次,但收费的孙宇晨财富自由革命之路也有536万次播放之多。

说起孙宇晨这档栏目,标签是90后亿万富翁,马云湖畔大学唯一90后学员,福布斯2015年中国30位30岁以下创业者等,年龄的强调非常明显。

通常来说,85前有孩子的概率超过了85后。

至于头牌栏目马东的奇葩团队,也是主讲个人成长、职场技巧等。而90后,早已踏上职场舞台。

情感类的内容卖得不是太出色,这可能和这类内容免费的极多有关,而陈见的女性性心理学,和年轻人相对更愿意主宰自己的性幸福,以及相关内容本身很稀缺有关。

 

另外一种内容。

田艺苗的古典音乐教学大卖,我一开始是很惊讶的。

我同样一开始也惊讶于这两日的中国古诗词大会的热议。

不过思考了一会儿,我开始理解这件事。

说得糙一点,这叫“装逼需求推动的内容付费”。

今天这个时代的年轻人,对精神上的追求并不低,甚至超过他们的70后前辈。道理就在于他们生长在富足经济下。不过恰恰可惜的是,在应试教育的指挥棒下,类似古典音乐、诗词赏析,并不是学校教育的主流。

这就非常能说通为什么田艺苗的古典音乐能大卖,刚刚上线的蒙曼品最美唐诗能迅速成为爆款了。

 

小结。

1、内容做成课程式,体系化,可能更容易售卖,需要聚焦聚焦再聚焦,并试运营进行调整;

2、85-90后是消费主体,内容应考虑他们的主要需求;

3、制作人本身是否有名不重要,重要的是能成体系输出,并与平台能充分合作;

4、部分学校不教授但与品位表达有关的素养教育,依然能大卖;

5、以上虽然是音频领域,但对于图文、视频,可能同样具有参考价值。

 

—— 首发 扯氮集 ——

版权声明 及 商业合作

作者执教于上海交通大学媒体与设计学院,天奇阿米巴创投基金投资合伙人

知识电商?不妨先看看已小成气候的付费音频,首发于扯氮集

]]>
0
<![CDATA[tensorflow distributed runtime初窥]]> http://www.udpwork.com/item/16121.html http://www.udpwork.com/item/16121.html#reviews Fri, 17 Feb 2017 00:13:00 +0800 sunchangming http://www.udpwork.com/item/16121.html
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0
<![CDATA[Windows下用copy命令合并文件的时候小心末尾的0x1A]]> http://www.udpwork.com/item/16127.html http://www.udpwork.com/item/16127.html#reviews Thu, 16 Feb 2017 22:56:00 +0800 sunchangming http://www.udpwork.com/item/16127.html
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0
<![CDATA[新浪微博劫持iTunes链接加入陌生推广码]]> http://www.udpwork.com/item/16117.html http://www.udpwork.com/item/16117.html#reviews Thu, 16 Feb 2017 16:23:17 +0800 ideawu http://www.udpwork.com/item/16117.html 有微博用户抱怨他在微博上面发布的带有自己推广码(可带来佣金收益), 都被微博替换成了某个陌生的推广码, 他自己的推广码被覆盖了!

@im61: 马勒戈壁的微博,我每天用 @PriceTag应用限免 这个账号发限免应用,链接里带了我的 iTunes 联盟令牌: 1001lsTF 和活动代码 wb_alet,结果发现几乎没有流量过来,刚刚 @图拉鼎 让我分析一下分享链接,大吃一惊啊,微博自动转换短链接,里面悄悄带上了自己的广告代码,我每天分享的收入都跑他口袋去了,无耻

我验证了下, 确实如此. 验证过程:

1. 使用新浪微博的官方网页发布一条带 itunes 链接的微博, 该链接并没有at(推广码)参数.
2. 微博发出后, 新浪微博使用其控制的短链接服务t.cn(whois注册人是 domainname@staff.sina.com.cn )为用户的原始链接生成了短链接.
3. 该短链接经过展开, 发现原始链接被非法加入了at参数. 该参数是iTunes推广码at=1001lbb6, 可为码的拥有者带来佣金收益.

这种行为的危害: 新浪微博的行为, 与运营商劫持一样恶劣, 在普通用户不知情的情况下, 带上自己的推广码, 非法获取佣金收益!

微博(新浪)私自在用户的链接里, 擅自带推广码(iTunes Affiliate Token), 该推广码可能带来佣金收益. 这种方法是被苹果禁止的, 相当于作弊. 大家应该向苹果官方举报, 对该at码进行封禁!

a0

a

b

Related posts:

  1. 账号系统的两阶段登录和三阶段登录
  2. Mac OSX下的看图软件Tovi
  3. 为什么iComet比nginx-push-stream-module更好?
]]>
有微博用户抱怨他在微博上面发布的带有自己推广码(可带来佣金收益), 都被微博替换成了某个陌生的推广码, 他自己的推广码被覆盖了!

@im61: 马勒戈壁的微博,我每天用 @PriceTag应用限免 这个账号发限免应用,链接里带了我的 iTunes 联盟令牌: 1001lsTF 和活动代码 wb_alet,结果发现几乎没有流量过来,刚刚 @图拉鼎 让我分析一下分享链接,大吃一惊啊,微博自动转换短链接,里面悄悄带上了自己的广告代码,我每天分享的收入都跑他口袋去了,无耻

我验证了下, 确实如此. 验证过程:

1. 使用新浪微博的官方网页发布一条带 itunes 链接的微博, 该链接并没有at(推广码)参数.
2. 微博发出后, 新浪微博使用其控制的短链接服务t.cn(whois注册人是 domainname@staff.sina.com.cn )为用户的原始链接生成了短链接.
3. 该短链接经过展开, 发现原始链接被非法加入了at参数. 该参数是iTunes推广码at=1001lbb6, 可为码的拥有者带来佣金收益.

这种行为的危害: 新浪微博的行为, 与运营商劫持一样恶劣, 在普通用户不知情的情况下, 带上自己的推广码, 非法获取佣金收益!

微博(新浪)私自在用户的链接里, 擅自带推广码(iTunes Affiliate Token), 该推广码可能带来佣金收益. 这种方法是被苹果禁止的, 相当于作弊. 大家应该向苹果官方举报, 对该at码进行封禁!

a0

a

b

Related posts:

  1. 账号系统的两阶段登录和三阶段登录
  2. Mac OSX下的看图软件Tovi
  3. 为什么iComet比nginx-push-stream-module更好?
]]>
0
<![CDATA[Windows下用copy命令合并文件的时候小心末尾的0x1A]]> http://www.udpwork.com/item/16106.html http://www.udpwork.com/item/16106.html#reviews Wed, 15 Feb 2017 22:59:00 +0800 sunchangming http://www.udpwork.com/item/16106.html
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0     0

udpwork.com 聚合 | 评论: 0 | 要! 要! 即刻! Now!

]]>
0
<![CDATA[知识电商这个市场会有多大]]> http://www.udpwork.com/item/16105.html http://www.udpwork.com/item/16105.html#reviews Wed, 15 Feb 2017 15:28:50 +0800 魏武挥 http://www.udpwork.com/item/16105.html 其实,我更愿意把知识电商说成是“内容付费”(或内容收费)。一来的确有些付费的内容,难登“知识”之大堂,二来,这个领域本来就是内容创业的分支,内容付费似乎更加符合脉络。不过这都是名词之争,就像某共享单车投资人在面对我问“产权归属运营方的单车哪里是共享呢?”这个问题时,笑曰:我们就不较真名词了吧?

商业圈,一向有这个传统。到今天,还有人笼而统之把几乎所有的微信公号都说成“自媒体”,我也懒得深究了。

 

 

昨日,在我写完《知识电商究竟是不是一个好东西》时,有两位朋友留言,都提及这样一个问题:知识电商能做多大?

我想,他们问的,应该是这个市场规模会有多大。

隔了个把小时,我给其中一个留言回复说:“我想了想,总量不好说,今年会井喷”。

井喷的意思,就是相对于2016年知识电商所实现的市场规模。

就我所知,知识电商大致上有四个平台,在2016年有一些可见的规模。

 

 

第一个是:得到。

得到上的规模比较容易查,在得到APP上,一共有18个收费专栏,基本上是199元/年,有两个例外,其一是王烁的,199元/9个月,另外一个是罗振宇自己的,1元/年。订户数都是公开的,各位有兴趣可以去总和一下。

第二个是,在行/分答。

比较难以知道这两个都是果壳出品的产品,究竟实现了多少流水。在行一度火过,我认识一位学心理学的美女博士,通过在行收到了十万以上的收入。但与得到和专栏五五分成不同,在行并没有提成。而分答,运营方有提成,一度比在行还火,进入去年年底后,略显沉寂。

第三个是,知乎live。

这个平台已经运营得很稳健,一共建立了17个类别,每个类别下多则过百,少则几十个。每个live,也有几十到几百不等的人数参与。票价从10元不到一直到400不等。这些都是公开可见的,有兴趣的,也可以去计算一下。

第四个是,喜马拉雅。

喜马拉雅位列中国最大的网络音频平台之一,于去年12月搞了一个“123知识狂欢节”,主攻收费音频。按其向媒体公布的数据是,当天实现5088万的流水——虽然这个数字看着实在有点那个,哈哈。但千万级别的流水应该不假。喜马拉雅上现在有很多收费音频节目,我一度还琢磨过要不要上去搞个读书栏目。

 

 

开年以来,36氪推出了收费专栏,第一批邀请到了当年互联网头号IT博客keso。当keso写下《我为什么现在开始出来卖以及这个公众号还会更新吗》一文,腾讯似乎有点坐不住了。

根据公号i黑马的披露,马化腾在keso朋友圈底下留言:应该等微信公众号付费订阅啊。keso则反馈你们这个测试太久了,马化腾旋即表示:我们争取加快。

去年8月,就开始流传微信要测试付费订阅,小半年过去后,看来腾讯真得要出手了。马化腾亲自过问的产品,历史上腾讯的进度就不会慢,现在应该也一样。

这可能是对这个市场规模的一种判断。

太小的生意,腾讯不会感兴趣的。

有几位腾讯略高层的朋友闲聊时开玩笑说,不过亿的生意,是上不了总裁办会议的。过亿都不一定。

 

 

我对内容收费这个市场的判断“井喷”,是基于对市场心理的估计。

在昨儿的文章里,我提到《民主与法制》这本杂志,在85年1月,发行了260万份。

85年,基本上是改革开放初期,已经有一部分人富了起来——比如农村里的所谓万元户(说起来这三个字是80年4月新华社一篇报道中首次使用并得到普及的),而有更多的人冲到追逐财富的行列中。社会上,谈赚钱已经不再是一个羞耻的事。

但同时,大学生依然是天之骄子(77年才恢复高考),社会民众普遍教育水平不高,最大的70后都还在学校里,60后深知自己年轻时的教育空白,整个社会,对求点知这件事并不反感,甚至有点趋之如骛。

不过,同样也要看到,85年那个时候,造原子弹不如卖茶叶蛋拿手术刀不如拿理发刀的说法也很普遍。人们虽然有求知欲,但未必对正经去读书有什么太高的期望。知识改变命运这句话,并未兴起。

当时的人们,可能不需要知识,但绝对需要内容,并愿意为之买单。

我昨儿已算过,2毛6分钱的一本杂志,在当年月入普遍不超过一百的前提下,还是占有一点比例的。如果我没记错的话,当年的冰棍四分钱一根吧,雪糕也不过八分钱。

80年代中后期到90年代,产生了不少百万发行的杂志,这些杂志基本上都是靠发行作为收入,广告不多,标准的依靠内容收费为生。

比如著名的凤姐口中的《知音》,创刊于1985年。被美国读者文摘盯上威胁要起诉后来不得不改名的《读者》,那就更早了,创刊于1981年。

 

 

家父在我昨儿那篇文章底下留言,认为当时做《民主与法制》的人,脑中其实根本没有生意两个字。

这个我同意。但这属于动机。我们还是要看结果。

那个时代,真的是重内容(有贪欲肯定希望摄入诸如技能法则技巧之类的精神食粮,或者是励志及鸡汤)轻知识(系统性念书的耐心是木有的),杂志业兴起,并不奇怪。

80年代,绝不是一个安静的年代,那是一个浮躁的年代。

闸门打开,亿万人向财富涌去。

 

 

互联网到来后,杂志业的好日子还没结束。2005年,中国互联网人口突破一个亿,但读者当年在某月的发行量依然能逼近千万之巨,不过,这个可能是它最辉煌的时刻,旋即迎来了衰退。

互联网的深化,使得越来越多的人成为网民,并由此而获得免费内容。需求虽然依然在,但有免费的,为什么要付费呢?

即便是打小就阅读报纸杂志的70后乃至60后,开始慢慢放弃纸媒,投身到互联网受众群体中。

内容收费,由此开始没落。

但内容这个需求,并没有没落。内容生产者们,需要一个契机,让他们再度王者归来。

 

 

2011年,科技圈非常有名的一个博客阮一峰,做了一个测试。

他在他的每篇博文底下,放上了一个支付按钮,一开始是0.99元人民币或0.99美元——用户读完后看着给,后来他发现由于国内支付系统所需要的手续费,使得0.99完全不现实,就上调为9.9元人民币。

测试一年后,2012年5月,他写下了这段文字:

从去年5月到今年5月,我一共写了88篇博客,共计收到1079笔付款,其中美元255.97元,人民币4106.04元。

紧接着,他写道:

这个数字算多吗?我的每篇博客,读者人数平均接近2万人。根据上面的统计,可以推算出,其中大概有12个人愿意向我付款。”转化率”(访问者转化为消费者的比率)不到0.1%,而电子商务网站的正常转化率大概是2%到5%。呜呼,付费阅读的艰难可见一斑。

我想,如此之低的转化率,说明目前阶段付费阅读还不可行。但是,另一方面,我们也必须看到,即使有各种不利条件,还是有人愿意付款。如果交易费用更低、支付过程更便利、付费后能得到实际利益,我觉得付费阅读是可行的。当然,前提是你的内容必须对读者有用。

需要注明一下,阮一峰是一个技术型博客,就是博文经常探讨技术上的技巧,甚至还有一些技术程序使用的教程,技能型内容很明显,我个人以为,也当得起“知识”二字。

阮一峰提到了付费阅读的三个条件,我再罗列一下:

交易费用更低、支付过程更便利、付费后能得到实际利益。

前两者,今天的移动互联网及其支付系统,已基本解决。

比如微信赞赏。

 

 

老实讲,今天中国的大学,和二十年前,真的不一样。

94-95年,是我在安徽那个三流大学里的最后一年,我依然记得课程满满,上课的那位外教还特别较真,我要是不到,他一定会问这个人跑哪里去了。直到95年上半年我大学四年最后一个学期,我的学校课程才算真正减少。

2007年,我进入交大这所国内一流大学执教的第二个年头,学院开始商量进行教学计划改革,改革的主要目标是调整课程安排,让所需教授的课程尽可能在一二年级解决,三年级课程已经很少,四年级可以说就是一个漫长的实习年。

真正的系统知识教育,至少从时间上,已经从四年压缩到了两年(这可能是文科的关系)。大学这么做,当然有客观原因。就业难,或者说,社会历练得越早可能对进入社会越有利,是做出如此安排的重要原因。

社会的运转速度是“加速型的”,这使得这个社会,同样很浮躁。

慢慢来是不行的。

在互联网一些细分领域,你会看到,半年定胜负的例子有的是。

 

 

我过去不太看好知识电商的一个原因在于,这是一个不断掏空自己的过程,总有一天,你的付费用户都被你养的都有一定内涵一定眼界了,你自己又掏空了,何以为继呢?

直到前两天,我忽然想明白了一个道理。

这个道理,写在了《这是一个加速的时代,但这里有个致命的问题》一文中。

供需双方,可能不是我以前所想象的那种。

那道鸿沟,普遍意义上,其实是越来越大,而不是越来越小。

因为有太多的人,是把门当成整个房间的。

 

 

小结一下:

1、当今社会土壤,恐惧与贪婪非常明显的并存,决定了人们对内容其实是有强烈需求的;

2、付费工具本身大幅进步,支付几块几十块可能连一秒钟都用不了;

3、加速时代,供需双方的鸿沟会变大,收费内容供给者得以为继。

不过,这里有个不过,

我坚信,整个互联网,依然是免费内容为主流。

但这不排斥,相对于2016年,2017年,将迎来知识电商也好,内容付费也好,的井喷。

 

—— 首发 扯氮集 ——

版权声明 及 商业合作

作者执教于上海交通大学媒体与设计学院,天奇阿米巴创投基金投资合伙人

知识电商这个市场会有多大,首发于扯氮集

]]>
其实,我更愿意把知识电商说成是“内容付费”(或内容收费)。一来的确有些付费的内容,难登“知识”之大堂,二来,这个领域本来就是内容创业的分支,内容付费似乎更加符合脉络。不过这都是名词之争,就像某共享单车投资人在面对我问“产权归属运营方的单车哪里是共享呢?”这个问题时,笑曰:我们就不较真名词了吧?

商业圈,一向有这个传统。到今天,还有人笼而统之把几乎所有的微信公号都说成“自媒体”,我也懒得深究了。

 

 

昨日,在我写完《知识电商究竟是不是一个好东西》时,有两位朋友留言,都提及这样一个问题:知识电商能做多大?

我想,他们问的,应该是这个市场规模会有多大。

隔了个把小时,我给其中一个留言回复说:“我想了想,总量不好说,今年会井喷”。

井喷的意思,就是相对于2016年知识电商所实现的市场规模。

就我所知,知识电商大致上有四个平台,在2016年有一些可见的规模。

 

 

第一个是:得到。

得到上的规模比较容易查,在得到APP上,一共有18个收费专栏,基本上是199元/年,有两个例外,其一是王烁的,199元/9个月,另外一个是罗振宇自己的,1元/年。订户数都是公开的,各位有兴趣可以去总和一下。

第二个是,在行/分答。

比较难以知道这两个都是果壳出品的产品,究竟实现了多少流水。在行一度火过,我认识一位学心理学的美女博士,通过在行收到了十万以上的收入。但与得到和专栏五五分成不同,在行并没有提成。而分答,运营方有提成,一度比在行还火,进入去年年底后,略显沉寂。

第三个是,知乎live。

这个平台已经运营得很稳健,一共建立了17个类别,每个类别下多则过百,少则几十个。每个live,也有几十到几百不等的人数参与。票价从10元不到一直到400不等。这些都是公开可见的,有兴趣的,也可以去计算一下。

第四个是,喜马拉雅。

喜马拉雅位列中国最大的网络音频平台之一,于去年12月搞了一个“123知识狂欢节”,主攻收费音频。按其向媒体公布的数据是,当天实现5088万的流水——虽然这个数字看着实在有点那个,哈哈。但千万级别的流水应该不假。喜马拉雅上现在有很多收费音频节目,我一度还琢磨过要不要上去搞个读书栏目。

 

 

开年以来,36氪推出了收费专栏,第一批邀请到了当年互联网头号IT博客keso。当keso写下《我为什么现在开始出来卖以及这个公众号还会更新吗》一文,腾讯似乎有点坐不住了。

根据公号i黑马的披露,马化腾在keso朋友圈底下留言:应该等微信公众号付费订阅啊。keso则反馈你们这个测试太久了,马化腾旋即表示:我们争取加快。

去年8月,就开始流传微信要测试付费订阅,小半年过去后,看来腾讯真得要出手了。马化腾亲自过问的产品,历史上腾讯的进度就不会慢,现在应该也一样。

这可能是对这个市场规模的一种判断。

太小的生意,腾讯不会感兴趣的。

有几位腾讯略高层的朋友闲聊时开玩笑说,不过亿的生意,是上不了总裁办会议的。过亿都不一定。

 

 

我对内容收费这个市场的判断“井喷”,是基于对市场心理的估计。

在昨儿的文章里,我提到《民主与法制》这本杂志,在85年1月,发行了260万份。

85年,基本上是改革开放初期,已经有一部分人富了起来——比如农村里的所谓万元户(说起来这三个字是80年4月新华社一篇报道中首次使用并得到普及的),而有更多的人冲到追逐财富的行列中。社会上,谈赚钱已经不再是一个羞耻的事。

但同时,大学生依然是天之骄子(77年才恢复高考),社会民众普遍教育水平不高,最大的70后都还在学校里,60后深知自己年轻时的教育空白,整个社会,对求点知这件事并不反感,甚至有点趋之如骛。

不过,同样也要看到,85年那个时候,造原子弹不如卖茶叶蛋拿手术刀不如拿理发刀的说法也很普遍。人们虽然有求知欲,但未必对正经去读书有什么太高的期望。知识改变命运这句话,并未兴起。

当时的人们,可能不需要知识,但绝对需要内容,并愿意为之买单。

我昨儿已算过,2毛6分钱的一本杂志,在当年月入普遍不超过一百的前提下,还是占有一点比例的。如果我没记错的话,当年的冰棍四分钱一根吧,雪糕也不过八分钱。

80年代中后期到90年代,产生了不少百万发行的杂志,这些杂志基本上都是靠发行作为收入,广告不多,标准的依靠内容收费为生。

比如著名的凤姐口中的《知音》,创刊于1985年。被美国读者文摘盯上威胁要起诉后来不得不改名的《读者》,那就更早了,创刊于1981年。

 

 

家父在我昨儿那篇文章底下留言,认为当时做《民主与法制》的人,脑中其实根本没有生意两个字。

这个我同意。但这属于动机。我们还是要看结果。

那个时代,真的是重内容(有贪欲肯定希望摄入诸如技能法则技巧之类的精神食粮,或者是励志及鸡汤)轻知识(系统性念书的耐心是木有的),杂志业兴起,并不奇怪。

80年代,绝不是一个安静的年代,那是一个浮躁的年代。

闸门打开,亿万人向财富涌去。

 

 

互联网到来后,杂志业的好日子还没结束。2005年,中国互联网人口突破一个亿,但读者当年在某月的发行量依然能逼近千万之巨,不过,这个可能是它最辉煌的时刻,旋即迎来了衰退。

互联网的深化,使得越来越多的人成为网民,并由此而获得免费内容。需求虽然依然在,但有免费的,为什么要付费呢?

即便是打小就阅读报纸杂志的70后乃至60后,开始慢慢放弃纸媒,投身到互联网受众群体中。

内容收费,由此开始没落。

但内容这个需求,并没有没落。内容生产者们,需要一个契机,让他们再度王者归来。

 

 

2011年,科技圈非常有名的一个博客阮一峰,做了一个测试。

他在他的每篇博文底下,放上了一个支付按钮,一开始是0.99元人民币或0.99美元——用户读完后看着给,后来他发现由于国内支付系统所需要的手续费,使得0.99完全不现实,就上调为9.9元人民币。

测试一年后,2012年5月,他写下了这段文字:

从去年5月到今年5月,我一共写了88篇博客,共计收到1079笔付款,其中美元255.97元,人民币4106.04元。

紧接着,他写道:

这个数字算多吗?我的每篇博客,读者人数平均接近2万人。根据上面的统计,可以推算出,其中大概有12个人愿意向我付款。”转化率”(访问者转化为消费者的比率)不到0.1%,而电子商务网站的正常转化率大概是2%到5%。呜呼,付费阅读的艰难可见一斑。

我想,如此之低的转化率,说明目前阶段付费阅读还不可行。但是,另一方面,我们也必须看到,即使有各种不利条件,还是有人愿意付款。如果交易费用更低、支付过程更便利、付费后能得到实际利益,我觉得付费阅读是可行的。当然,前提是你的内容必须对读者有用。

需要注明一下,阮一峰是一个技术型博客,就是博文经常探讨技术上的技巧,甚至还有一些技术程序使用的教程,技能型内容很明显,我个人以为,也当得起“知识”二字。

阮一峰提到了付费阅读的三个条件,我再罗列一下:

交易费用更低、支付过程更便利、付费后能得到实际利益。

前两者,今天的移动互联网及其支付系统,已基本解决。

比如微信赞赏。

 

 

老实讲,今天中国的大学,和二十年前,真的不一样。

94-95年,是我在安徽那个三流大学里的最后一年,我依然记得课程满满,上课的那位外教还特别较真,我要是不到,他一定会问这个人跑哪里去了。直到95年上半年我大学四年最后一个学期,我的学校课程才算真正减少。

2007年,我进入交大这所国内一流大学执教的第二个年头,学院开始商量进行教学计划改革,改革的主要目标是调整课程安排,让所需教授的课程尽可能在一二年级解决,三年级课程已经很少,四年级可以说就是一个漫长的实习年。

真正的系统知识教育,至少从时间上,已经从四年压缩到了两年(这可能是文科的关系)。大学这么做,当然有客观原因。就业难,或者说,社会历练得越早可能对进入社会越有利,是做出如此安排的重要原因。

社会的运转速度是“加速型的”,这使得这个社会,同样很浮躁。

慢慢来是不行的。

在互联网一些细分领域,你会看到,半年定胜负的例子有的是。

 

 

我过去不太看好知识电商的一个原因在于,这是一个不断掏空自己的过程,总有一天,你的付费用户都被你养的都有一定内涵一定眼界了,你自己又掏空了,何以为继呢?

直到前两天,我忽然想明白了一个道理。

这个道理,写在了《这是一个加速的时代,但这里有个致命的问题》一文中。

供需双方,可能不是我以前所想象的那种。

那道鸿沟,普遍意义上,其实是越来越大,而不是越来越小。

因为有太多的人,是把门当成整个房间的。

 

 

小结一下:

1、当今社会土壤,恐惧与贪婪非常明显的并存,决定了人们对内容其实是有强烈需求的;

2、付费工具本身大幅进步,支付几块几十块可能连一秒钟都用不了;

3、加速时代,供需双方的鸿沟会变大,收费内容供给者得以为继。

不过,这里有个不过,

我坚信,整个互联网,依然是免费内容为主流。

但这不排斥,相对于2016年,2017年,将迎来知识电商也好,内容付费也好,的井喷。

 

—— 首发 扯氮集 ——

版权声明 及 商业合作

作者执教于上海交通大学媒体与设计学院,天奇阿米巴创投基金投资合伙人

知识电商这个市场会有多大,首发于扯氮集

]]>
0