Tại sao không thể có bất kỳ chuyển đổi ngầm?


14

Theo tôi hiểu, chuyển đổi ngầm có thể gây ra lỗi.

Nhưng điều đó không có ý nghĩa - không nên chuyển đổi bình thường cũng gây ra lỗi?

Tại sao không có

len(100)

làm việc bằng cách giải thích ngôn ngữ (hoặc biên dịch) nó như

len(str(100))

đặc biệt vì đó là cách duy nhất (tôi biết) để nó hoạt động. Ngôn ngữ biết lỗi là gì, tại sao không sửa nó?

Trong ví dụ này, tôi đã sử dụng Python, mặc dù tôi cảm thấy rằng đối với một cái gì đó nhỏ bé này về cơ bản là phổ biến.


2
perl -e 'print length(100);'bản in 3.

2
Và do đó, bản chất của ngôn ngữ và hệ thống loại của nó. Đó là một phần của thiết kế của trăn.

2
Nó không sửa nó, vì nó không biết ý của bạn. có lẽ bạn muốn làm một cái gì đó hoàn toàn khác nhau. như kiện một vòng lặp nhưng chưa bao giờ làm bất cứ điều gì như lập trình trước đây. Vì vậy, nếu nó tự sửa nó, người dùng sẽ không biết mình đã sai cũng như việc cấy ghép sẽ không làm những gì anh ta mong đợi.
Zaibis

5
@PieCrust Python được chuyển đổi như thế nào? Điều không đúng là tất cả các chuyển đổi có thể sẽ trả về cùng một kết quả.
Bakuriu

7
@PieCrust: chuỗi và mảng là iterables. Tại sao lại strlà cách ngầm để chuyển đổi một int thành một iterable? làm thế nào về range? hoặc bin( hex, oct) hoặc chr(hoặc unichr)? Tất cả những lần lặp lại đó, ngay cả khi strcó vẻ rõ ràng nhất đối với bạn trong tình huống này.
njzk2

Câu trả lời:


37

Đối với những gì nó có giá trị len(str(100)), len(chr(100))len(hex(100))tất cả đều khác nhau. strkhông cách duy nhất để làm cho nó làm việc, kể từ khi có nhiều hơn một chuyển đổi khác nhau trong Python từ một số nguyên thành một chuỗi. Một trong số đó tất nhiên là phổ biến nhất, nhưng không nhất thiết phải đi mà không nói đó là ý bạn. Một chuyển đổi ngầm có nghĩa đen là "nó không cần phải nói".

Một trong những vấn đề thực tế với chuyển đổi ngầm định là không phải lúc nào cũng rõ ràng việc chuyển đổi nào sẽ được áp dụng khi có một số khả năng và điều này dẫn đến việc người đọc mắc lỗi khi diễn giải mã vì họ không tìm ra chuyển đổi ngầm định chính xác. Mọi người luôn nói rằng những gì họ dự định là "giải thích rõ ràng". Điều đó rõ ràng với họ bởi vì đó là những gì họ muốn nói. Nó có thể không rõ ràng với người khác.

Đây là lý do tại sao (hầu hết thời gian) Python thích rõ ràng để ẩn, nó thích không mạo hiểm. Trường hợp chính trong đó Python thực hiện kiểu ép buộc là trong số học. Nó cho phép 1 + 1.0vì thay thế sẽ là quá khó chịu để sống với, nhưng nó không cho phép 1 + "1"vì nó nghĩ rằng bạn cần phải xác định xem bạn có nghĩa là int("1"), float("1"), ord("1"), str(1) + "1", hay cái gì khác. Nó cũng không cho phép (1,2,3) + [4,5,6], mặc dù nó có thể xác định quy tắc để chọn loại kết quả, giống như nó xác định quy tắc để chọn loại kết quả 1 + 1.0.

Các ngôn ngữ khác không đồng ý và có rất nhiều chuyển đổi ngầm. Chúng càng bao gồm, chúng càng trở nên ít rõ ràng. Hãy thử ghi nhớ các quy tắc từ tiêu chuẩn C cho "khuyến mãi số nguyên" và "chuyển đổi số học thông thường" trước khi ăn sáng!


+1 Để chứng minh làm thế nào giả định bắt đầu, lenchỉ có thể hoạt động với một loại, về cơ bản là thiếu sót.
KChaloux

2
@KChaloux: thực sự, trong ví dụ của tôi str, chrhexlàm tất cả trả về cùng một loại! Tôi đã cố gắng nghĩ về một loại khác mà người xây dựng của họ có thể chỉ cần một int, nhưng tôi chưa nghĩ ra bất cứ điều gì. Lý tưởng sẽ là một số container Xtrong đó len(X(100)) == 100;-) numpy.zeroscó vẻ hơi mơ hồ.
Steve Jessop

2
Đối với ví dụ về những vấn đề có thể thấy điều này: docs.google.com/document/d/...destroyallsoftware.com/talks/wat
Jens Schauder

1
Chuyển đổi ngầm (Tôi nghĩ rằng đây được gọi là ép buộc) có thể gây ra những thứ khó chịu giống như có a == b, b == cnhưng a == ckhông phải là sự thật.
bgusach

Câu trả lời tuyệt vời. Tất cả mọi thứ tôi muốn nói, chỉ từ tốt hơn! Vấn đề nhỏ duy nhất của tôi là các chương trình khuyến mãi số nguyên C khá đơn giản so với việc ghi nhớ các quy tắc cưỡng chế JavaScript hoặc quấn đầu quanh một cơ sở mã C ++ với việc sử dụng các hàm tạo ẩn.
GrandOpener

28

Theo tôi hiểu, chuyển đổi ngầm có thể gây ra lỗi.

Bạn đang thiếu một từ: chuyển đổi ngầm có thể gây ra lỗi thời gian chạy .

Đối với một trường hợp đơn giản như bạn thể hiện, nó khá rõ ràng ý của bạn. Nhưng ngôn ngữ không thể làm việc trong các trường hợp. Họ cần phải làm việc với các quy tắc. Đối với nhiều tình huống khác, không rõ liệu lập trình viên có lỗi (sử dụng sai loại) hay nếu lập trình viên có ý định làm những gì mà mã giả định họ dự định làm.

Nếu mã giả định sai, bạn sẽ gặp lỗi thời gian chạy. Vì chúng tẻ nhạt để theo dõi, nhiều ngôn ngữ sai lầm khi nói với bạn rằng bạn đã nhắn tin và cho phép bạn nói với máy tính những gì bạn thực sự có ý nghĩa (sửa lỗi hoặc thực hiện chuyển đổi). Các ngôn ngữ khác đưa ra dự đoán, vì phong cách của họ cho vay mã nhanh chóng và dễ dàng để dễ gỡ lỗi hơn.

Một điều cần lưu ý là các chuyển đổi ngầm định làm cho trình biên dịch phức tạp hơn một chút. Bạn cần cẩn thận hơn về các chu kỳ (hãy thử chuyển đổi từ A sang B; rất tiếc là không hoạt động, nhưng có một chuyển đổi từ B sang A! Và sau đó bạn cũng cần lo lắng về các chu kỳ có kích thước C và D ), trong đó là một động lực nhỏ để tránh chuyển đổi ngầm.


Chủ yếu là nói về các chức năng chỉ có thể hoạt động với một loại, làm cho việc chuyển đổi trở nên rõ ràng. Điều gì sẽ là một trong đó sẽ làm việc với nhiều loại, và loại bạn đưa vào làm cho một sự khác biệt? print ("295") = print (295), do đó sẽ không tạo ra sự khác biệt, ngoại trừ với các biến. Những gì bạn nói có ý nghĩa, ngoại trừ đoạn 3d ... Bạn có thể nhắc lại không?
Quelklef

30
@PieCrust - 123 + "456", bạn muốn "123456" hay 579? Các ngôn ngữ lập trình không thực hiện ngữ cảnh, vì vậy rất khó để họ "tìm ra", vì họ sẽ cần biết bối cảnh mà việc bổ sung đang được thực hiện. Đoạn nào không rõ ràng?
Telastyn

1
Ngoài ra, có thể giải thích là cơ sở 10 không phải là điều bạn muốn. Đúng, chuyển đổi ngầm là một điều tốt , nhưng chỉ khi chúng không thể che giấu lỗi.
Ded repeatator

13
@PieCrust: Thành thật mà nói tôi sẽ không bao giờ mong đợi len(100)trở lại 3. Tôi sẽ thấy nó trực quan hơn nhiều khi tính toán số bit (hoặc byte) trong biểu diễn 100.
dùng541686

3
Tôi với @Mehrdad, từ ví dụ của bạn, rõ ràng bạn nghĩ rằng rõ ràng 100% len(100)sẽ cung cấp cho bạn số lượng ký tự đại diện thập phân của số 100. Nhưng đó thực sự là một tấn giả định mà bạn không biết của họ. Bạn có thể đưa ra các đối số mạnh tại sao số bit hoặc byte hoặc ký tự của biểu diễn thập lục phân ( 64-> 2 ký tự) phải được trả về len().
funkwurm

14

Chuyển đổi ngầm là hoàn toàn có thể làm. Tình huống mà bạn gặp rắc rối là khi bạn không biết cách nào đó nên hoạt động.

Một ví dụ về điều này có thể được nhìn thấy trong Javascript nơi người +vận hành làm việc theo những cách khác nhau vào những thời điểm khác nhau.

>>> 4 + 3
7
>>> "4" + 3
43
>>> 4 + "3"
43

Nếu một trong các đối số là một chuỗi, thì +toán tử là một chuỗi nối, nếu không nó là phép cộng.

Nếu bạn được cung cấp một đối số và không biết đó là một chuỗi hay một số nguyên và muốn thực hiện bổ sung với nó, nó có thể là một mớ hỗn độn.

Một cách khác để giải quyết vấn đề này là từ di sản Cơ bản (mà perl theo sau - xem Lập trình là Khó, Hãy viết kịch bản ... )

Trong Cơ bản, lenhàm chỉ có nghĩa là được gọi trên Chuỗi (tài liệu cho trực quan cơ bản : "Bất kỳ biểu thức hoặc tên biến hợp lệ nào. Nếu Biểu thức là loại Object, hàm Len sẽ trả về kích thước vì nó sẽ được ghi vào tệp bởi chức năng FilePut. ").

Perl theo khái niệm này của bối cảnh. Sự nhầm lẫn tồn tại trong JavaScript với các loại chuyển đổi ngầm định cho +toán tử đôi khi là phép cộng và đôi khi phép nối không xảy ra trong perl vì luôn luôn+ là phép cộng và luôn luôn là phép nối..

Nếu một cái gì đó được sử dụng trong ngữ cảnh vô hướng, thì nó là vô hướng (ví dụ: sử dụng danh sách làm vô hướng, danh sách này hoạt động như thể nó là một số tương ứng với độ dài của nó). Nếu bạn sử dụng toán tử chuỗi ( eqđể kiểm tra đẳng thức, cmpđể so sánh chuỗi) thì vô hướng được sử dụng như thể đó là một chuỗi. Tương tự, nếu một cái gì đó được sử dụng trong ngữ cảnh toán học ( ==để kiểm tra đẳng thức và <=>so sánh số), thì vô hướng được sử dụng như thể nó là một số.

Nguyên tắc cơ bản cho tất cả các chương trình là "làm điều gây ngạc nhiên cho người đó ít nhất". Điều này không có nghĩa là không có bất ngờ trong đó, nhưng nỗ lực là làm cho người đó ngạc nhiên ít nhất.

Đi đến một người anh em họ thân thiết của perl - php, có những tình huống mà một nhà điều hành có thể hành động trên một cái gì đó trong bối cảnh chuỗi hoặc số và hành vi có thể gây ngạc nhiên cho mọi người. Các ++nhà điều hành là một ví dụ như vậy. Về số lượng, nó hoạt động chính xác như mong đợi. Khi hành động trên một chuỗi, chẳng hạn như "aa", nó sẽ tăng chuỗi ( $foo = "aa"; $foo++; echo $foo;in ab). Nó cũng sẽ cuộn lại để azkhi tăng lên ba. Điều này không đặc biệt đáng ngạc nhiên.

$foo = "3d8";
echo "$foo\n";
$foo++;
echo "$foo\n";
$foo++;
echo "$foo\n";
$foo++;
echo "$foo\n";

( nhàn rỗi )

Điều này in ra:

3d8
3d9
3e0
4

Chào mừng bạn đến sự nguy hiểm của các chuyển đổi ngầm định và các toán tử hoạt động khác nhau trên cùng một chuỗi. (Perl xử lý khối mã đó một chút khác biệt - nó quyết định rằng "3d8"khi ++toán tử được áp dụng là một giá trị số từ đầu và chuyển sang 4ngay ( ideone ) - hành vi này được mô tả tốt trong perlop: Tự động tăng và Tự động giảm )

Bây giờ, tại sao một ngôn ngữ làm một cái gì đó theo cách này và cách khác làm theo cách khác theo suy nghĩ thiết kế của các nhà thiết kế. Triết lý của Perl là có nhiều hơn một cách để làm điều đó - và tôi có thể nghĩ ra một số cách để thực hiện một số thao tác này. Mặt khác, Python có một triết lý được mô tả trong PEP 20 - Zen of Python nói (trong số những thứ khác): "Nên có một - và tốt nhất là chỉ có một cách rõ ràng để làm điều đó."

Những khác biệt thiết kế đã dẫn đến các ngôn ngữ khác nhau. Có một cách để lấy độ dài của một số trong Python. Chuyển đổi ngầm đi ngược lại triết lý này.

Đọc liên quan: Tại sao Ruby không chuyển đổi ngầm định Fixnum thành Chuỗi?


IMHO, một ngôn ngữ / khung tốt nên thường có nhiều cách thực hiện các cách xử lý các trường hợp góc chung khác nhau (tốt hơn để xử lý trường hợp góc chung một lần trong một ngôn ngữ hoặc khung hơn 1000 lần trong 1000 chương trình); thực tế là hai nhà khai thác làm cùng một điều hầu hết thời gian không nên được coi là một điều xấu, nếu khi hành vi của họ khác nhau sẽ có lợi ích cho mỗi biến thể. Không khó để so sánh hai biến số xytheo cách mang lại mối quan hệ hoặc xếp hạng tương đương, nhưng các toán tử so sánh của IIRC Python ...
supercat

... Thực hiện không quan hệ tương đương cũng như xếp hạng và Python không cung cấp bất kỳ toán tử thuận tiện nào.
supercat

12

ColdFusion đã làm hầu hết điều này. Nó xác định một bộ quy tắc để xử lý các chuyển đổi ngầm định của bạn, có một loại biến duy nhất và bạn sẽ đến đó.

Kết quả là vô chính phủ, trong đó thêm "4a" vào 6 là 6.16667.

Tại sao? Vâng, bởi vì biến đầu tiên trong hai biến là một số, vì vậy kết quả sẽ là số. "4a" được phân tích cú pháp dưới dạng ngày và được xem là "4 giờ sáng". 4 giờ sáng là 4: 00/24: 00 hoặc 1/6 của một ngày (0.16667). Thêm vào 6 và bạn nhận được 6.16667.

Danh sách có các ký tự phân tách mặc định của dấu phẩy, vì vậy nếu bạn từng thêm một mục vào danh sách có chứa dấu phẩy, thì bạn chỉ cần thêm hai mục. Ngoài ra, danh sách là các chuỗi bí mật, vì vậy chúng có thể được phân tích cú pháp dưới dạng ngày nếu chúng chỉ chứa 1 mục.

Kiểm tra so sánh chuỗi nếu cả hai chuỗi có thể được phân tích cú pháp đến ngày đầu tiên. Bởi vì không có loại ngày, nó làm điều đó. Điều tương tự đối với các số và chuỗi chứa số, ký hiệu bát phân và booleans ("đúng" và "có", v.v.)

Thay vì thất bại nhanh chóng và báo cáo lỗi, thay vào đó ColdFusion sẽ xử lý các chuyển đổi kiểu dữ liệu cho bạn. Và không phải trong một cách tốt.

Tất nhiên, bạn có thể sửa các chuyển đổi ngầm định bằng cách gọi các hàm rõ ràng như DateCompare ... nhưng sau đó bạn đã mất "lợi ích" của chuyển đổi ngầm.


Đối với ColdFusion, đây là một cách giúp trao quyền cho các nhà phát triển. Đặc biệt là khi tất cả các nhà phát triển đã quen với HTML (ColdFusion hoạt động với các thẻ, nó được gọi là CFML, sau này họ cũng đã thêm tập lệnh thông qua <cfscript>, nơi bạn không cần thêm thẻ cho mọi thứ). Và nó hoạt động tốt khi bạn chỉ muốn làm cho một cái gì đó làm việc. Nhưng khi bạn cần mọi thứ diễn ra theo cách chính xác hơn, bạn cần một ngôn ngữ từ chối thực hiện chuyển đổi ngầm cho bất cứ điều gì có vẻ như nó có thể là một sai lầm.


1
wow, "vô chính phủ" thực sự!
hoosierEE

7

Bạn đang nói rằng chuyển đổi ngầm có thể là một ý tưởng tốt cho các hoạt động không rõ ràng, như int a = 100; len(a), nơi bạn rõ ràng có nghĩa là chuyển đổi int thành một chuỗi trước khi gọi.

Nhưng bạn đang quên rằng các cuộc gọi này có thể không rõ ràng về mặt cú pháp , nhưng chúng có thể đại diện cho một lỗi đánh máy được thực hiện bởi người lập trình có ý định vượt qua a1, đó một chuỗi. Đây là một ví dụ giả định, nhưng với các IDE cung cấp tự động hoàn thành cho các tên biến, những lỗi này xảy ra.

Hệ thống loại nhằm mục đích giúp chúng tôi tránh lỗi và đối với các ngôn ngữ chọn kiểm tra loại nghiêm ngặt hơn, các chuyển đổi ngầm sẽ làm suy yếu điều đó.

Kiểm tra tai ương của Javascript với tất cả các chuyển đổi ngầm định được thực hiện bởi ==, đến mức nhiều người hiện khuyên bạn nên gắn bó với toán tử không chuyển đổi ẩn ===.


6

Hãy xem xét một lúc bối cảnh của tuyên bố của bạn. Bạn nói rằng đây là "cách duy nhất" nó có thể hoạt động, nhưng bạn có thực sự chắc chắn rằng nó sẽ hoạt động như vậy không? Cái này thì sao:

def approx_log_10(s):
    return len(s)
print approx_log_10(3.5)  # "3" is probably not what I'm expecting here...

Như những người khác đã đề cập, trong ví dụ rất cụ thể của bạn, trình biên dịch có vẻ đơn giản với ý nghĩa của bạn. Nhưng trong bối cảnh lớn hơn của các chương trình phức tạp hơn, thường không rõ ràng cho dù bạn định nhập một chuỗi, hoặc đánh máy một tên biến cho một tên khác, hoặc gọi hàm sai, hoặc bất kỳ lỗi nào trong số hàng chục lỗi logic tiềm năng khác .

Trong trường hợp trình thông dịch / trình biên dịch đoán được ý của bạn, đôi khi nó hoạt động một cách kỳ diệu, và sau đó những lần khác bạn dành hàng giờ để gỡ lỗi một cái gì đó bí ẩn không hoạt động mà không có lý do rõ ràng. Trong trường hợp trung bình, sẽ an toàn hơn nhiều khi nói rằng tuyên bố đó không có ý nghĩa và dành vài giây để sửa nó thành những gì bạn thực sự muốn nói.


3

Chuyển đổi tiềm ẩn có thể là một nỗi đau thực sự để làm việc với. Trong PowerShell:

$a = $(dir *.xml) # typeof a is a list, because there are two XML files in the folder.
$a = $(dir *.xml) # typeof a is a string, because there is one XML file in the folder.

Đột nhiên, có gấp đôi số thử nghiệm cần thiết và gấp đôi số lỗi có thể xảy ra nếu không có chuyển đổi ngầm định.


2

Diễn viên rõ ràng rất quan trọng vì họ đang làm rõ ý định của bạn. Trước hết, sử dụng phôi rõ ràng kể một câu chuyện cho ai đó đang đọc mã của bạn. Họ tiết lộ rằng bạn cố ý làm những gì bạn đã làm. Hơn nữa, áp dụng tương tự cho trình biên dịch. Ví dụ sau đây là bất hợp pháp trong C #

double d = 3.1415926;
// code elided
int i = d;

Các diễn viên sẽ làm cho bạn mất độ chính xác, có thể dễ dàng là một lỗi. Do đó trình biên dịch từ chối biên dịch. Bằng cách sử dụng một dàn diễn viên rõ ràng, bạn đang nói với trình biên dịch: "Này, tôi biết tôi đang làm gì." Và anh sẽ đi: "Được!" Và biên dịch.


1
Trên thực tế, tôi không thích hầu hết các diễn viên rõ ràng hơn những diễn viên ngầm; IMHO, điều duy nhất (X)ynên có nghĩa là "Tôi nghĩ rằng Y có thể chuyển đổi thành X; thực hiện chuyển đổi nếu có thể hoặc ném ngoại lệ nếu không"; nếu một chuyển đổi thành công, người ta sẽ có thể dự đoán giá trị kết quả sẽ là * mà không cần phải biết bất kỳ quy tắc đặc biệt nào về chuyển đổi từ loại ysang loại X. Nếu được yêu cầu chuyển đổi -1,5 và 1,5 sang số nguyên, một số ngôn ngữ sẽ mang lại (-2,1); một số (-2,2), một số (-1,1) và một số (-1,2). Trong khi các quy tắc của C hầu như không được biết đến ...
supercat

1
... loại chuyển đổi đó có vẻ như sẽ được thực hiện một cách phù hợp thông qua phương thức hơn là thông qua cast (quá tệ .NET không bao gồm các hàm chuyển đổi tròn và hiệu quả và Java bị quá tải theo kiểu hơi ngớ ngẩn).
supercat

Bạn đang nói với trình biên dịch " Tôi nghĩ tôi biết những gì tôi đang làm".
gnasher729

@ gnasher729 Có một số sự thật trong đó :)
Paul Kertscher

1

Các ngôn ngữ hỗ trợ chuyển đổi / chuyển đổi ngầm định hoặc gõ yếu như đôi khi được nhắc đến, sẽ đưa ra các giả định cho bạn rằng không luôn khớp với hành vi bạn dự định hoặc mong đợi. Các nhà thiết kế ngôn ngữ có thể đã có quá trình suy nghĩ giống như bạn đã làm đối với một loại chuyển đổi hoặc chuỗi chuyển đổi nhất định và trong trường hợp đó bạn sẽ không bao giờ gặp sự cố; tuy nhiên nếu họ không làm thì chương trình của bạn sẽ thất bại.

Sự thất bại, tuy nhiên, có thể hoặc không rõ ràng. Vì các diễn viên ngầm này xảy ra trong thời gian chạy, chương trình của bạn sẽ chạy rất vui vẻ và bạn sẽ chỉ thấy một vấn đề khi bạn nhìn vào đầu ra hoặc kết quả của chương trình. Một ngôn ngữ yêu cầu các diễn viên rõ ràng (một số gọi đây là gõ mạnh) sẽ gây ra lỗi cho bạn trước khi chương trình bắt đầu thực thi, đây là một vấn đề rõ ràng và dễ dàng hơn nhiều để khắc phục các lỗi ẩn đó.

Hôm nọ, một người bạn của tôi hỏi anh ấy 2 tuổi 20 + 20 là gì và anh ấy đã trả lời năm 2020. Tôi đã nói với bạn tôi rằng "Anh ấy sẽ trở thành một lập trình viên Javascript".

Trong Javascript:

20+20
40

20+"20"
"2020"

Do đó, bạn có thể thấy các vấn đề mà các diễn viên ngầm có thể tạo ra và tại sao nó không phải là thứ có thể sửa được. Cách khắc phục tôi sẽ tranh luận, là sử dụng phôi rõ ràng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.