Một hàm gọi Math.random () có thuần túy không?


112

Sau đây có phải là một hàm thuần túy không?

function test(min,max) {
   return  Math.random() * (max - min) + min;
}

Sự hiểu biết của tôi là một hàm thuần túy tuân theo các điều kiện sau:

  1. Nó trả về một giá trị được tính từ các tham số
  2. Nó không thực hiện bất kỳ công việc nào ngoài việc tính toán giá trị trả về

Nếu định nghĩa này đúng, hàm của tôi có phải là một hàm thuần túy không? Hay sự hiểu biết của tôi về những gì định nghĩa một hàm thuần túy không chính xác?


66
"Nó không thực hiện bất kỳ công việc nào khác ngoài việc tính toán giá trị trả về" Nhưng nó gọi là Math.random()thay đổi trạng thái của RNG.
Paul Draper,

1
Điểm thứ hai giống như "nó không thay đổi trạng thái bên ngoài (đối với chức năng)"; và là người đầu tiên nên được bổ sung phần nào giống như "nó trả về giá trị CÙNG tính toán từ các thông số CÙNG", khi mọi người đã được viết dưới đây
MVCDS

Có một khái niệm về một hàm semipure, cho phép tính ngẫu nhiên? Ví dụ: test(a,b)luôn trả về cùng một đối tượng Random(a,b)(có thể đại diện cho các số cụ thể khác nhau)? Nếu bạn giữ Randombiểu tượng nó là thuần túy theo nghĩa cổ điển, nếu bạn đánh giá nó sớm và đưa vào số, có thể như một loại tối ưu hóa, hàm vẫn giữ được một số "thuần túy".
jdm

1
"Bất cứ ai coi các phương pháp số học tạo ra các chữ số ngẫu nhiên, tất nhiên là ở trong tình trạng tội lỗi." - John von Neumann
Steve Kuo

1
@jdm nếu bạn làm theo chủ đề "bán thuần túy", nơi bạn coi các hàm thuần túy là modulo một số tác dụng phụ được xác định rõ ràng , bạn có thể sẽ phát minh ra monads. Chúc mừng về đội phản diện. > :)
luqui

Câu trả lời:


185

Không, không phải. Với cùng một đầu vào, hàm này sẽ trả về các giá trị khác nhau. Và sau đó bạn không thể xây dựng một 'bảng' ánh xạ đầu vào và đầu ra.

Từ bài viết Wikipedia về chức năng Pure :

Hàm luôn đánh giá cùng một giá trị kết quả với cùng (các) giá trị đối số. Giá trị kết quả của hàm không thể phụ thuộc vào bất kỳ thông tin hoặc trạng thái ẩn nào có thể thay đổi trong khi tiến hành chương trình hoặc giữa các lần thực thi khác nhau của chương trình, cũng như không thể phụ thuộc vào bất kỳ đầu vào bên ngoài nào từ các thiết bị I / O

Ngoài ra, một điều khác là một hàm thuần túy có thể được thay thế bằng một bảng biểu thị ánh xạ từ đầu vào và đầu ra, như được giải thích trong chủ đề này .

Nếu bạn muốn viết lại hàm này và thay đổi nó thành một hàm thuần túy, bạn cũng nên chuyển giá trị ngẫu nhiên làm đối số

function test(random, min, max) {
   return random * (max - min) + min;
}

và sau đó gọi nó theo cách này (ví dụ, với 2 và 5 là tối thiểu và tối đa):

test( Math.random(), 2, 5)

2
Điều gì sẽ xảy ra nếu bạn phải gieo lại trình tạo ngẫu nhiên mỗi lần bên trong hàm trước khi gọi Math.random?
cs95

16
@ cᴏʟᴅsᴘᴇᴇᴅ Ngay cả khi đó, nó vẫn có tác dụng phụ (thay đổi Math.randomsản lượng trong tương lai ); để nó trở nên thuần khiết, bạn phải bằng cách nào đó lưu trạng thái RNG hiện tại, gửi lại nó, gọi Math.randomvà khôi phục nó về trạng thái trước đó.
LegionMammal978

2
@ cᴏʟᴅsᴘᴇᴇᴅ Tất cả RNG được tính toán đều dựa trên sự ngẫu nhiên giả mạo. Có thứ gì đó đang chạy bên dưới khiến nó xuất hiện ngẫu nhiên và bạn không thể giải thích điều đó, khiến nó trở nên không tinh khiết. Ngoài ra, và có lẽ quan trọng hơn đối với câu hỏi của bạn, bạn không thể gieo rắc Math.random
zfrisch

14
@ LegionMammal978… và làm như vậy một cách nguyên tử.
tiền ký quỹ

2
@ cᴏʟᴅsᴘᴇᴇᴅ Có nhiều cách để có RNG hoạt động với các chức năng thuần túy, nhưng nó liên quan đến việc chuyển trạng thái RNG cho hàm và để hàm trả về trạng thái RNG thay thế, đó là cách Haskell (một ngôn ngữ lập trình hàm thực thi chức năng thuần khiết) đạt được nó.
Pharap

50

Câu trả lời đơn giản cho câu hỏi của bạn là Math.random()vi phạm quy tắc số 2.

Nhiều câu trả lời khác ở đây đã chỉ ra rằng sự hiện diện của Math.random()nghĩa là chức năng này không thuần túy. Nhưng tôi nghĩ điều đáng nói là tại sao các Math.random() chức năng lại sử dụng nó.

Giống như tất cả các trình tạo số giả ngẫu nhiên, Math.random()bắt đầu bằng giá trị "hạt giống". Sau đó, nó sử dụng giá trị đó làm điểm bắt đầu cho một chuỗi các thao tác bit cấp thấp hoặc các thao tác khác dẫn đến kết quả đầu ra không thể đoán trước (nhưng không thực sự ngẫu nhiên ).

Trong JavaScript, quá trình liên quan phụ thuộc vào việc triển khai và không giống như nhiều ngôn ngữ khác, JavaScript không cung cấp cách nào để chọn hạt giống :

Việc thực hiện chọn hạt giống ban đầu cho thuật toán tạo số ngẫu nhiên; nó không thể được chọn hoặc thiết lập lại bởi người dùng.

Đó là lý do tại sao hàm này không thuần túy: JavaScript về cơ bản đang sử dụng một tham số hàm ngầm mà bạn không có quyền kiểm soát. Nó đọc thông số đó từ dữ liệu được tính toán và lưu trữ ở nơi khác, và do đó vi phạm quy tắc số 2 trong định nghĩa của bạn.

Nếu bạn muốn biến đây thành một hàm thuần túy, bạn có thể sử dụng một trong các trình tạo số ngẫu nhiên thay thế được mô tả ở đây . Gọi máy phát điện đó seedable_random. Nó nhận một tham số (hạt giống) và trả về một số "ngẫu nhiên". Tất nhiên, con số này không thực sự ngẫu nhiên chút nào; nó được xác định duy nhất bởi hạt giống. Đó là lý do tại sao đây là một chức năng thuần túy. Đầu ra của seedable_randomchỉ là "ngẫu nhiên" theo nghĩa là khó dự đoán kết quả dựa trên đầu vào.

Phiên bản thuần túy của hàm này sẽ cần có ba tham số:

function test(min, max, seed) {
   return  seedable_random(seed) * (max - min) + min;
}

Đối với bất kỳ bộ ba (min, max, seed)tham số đã cho nào, điều này sẽ luôn trả về cùng một kết quả.

Lưu ý rằng nếu bạn muốn đầu ra của seedable_randomthực sự ngẫu nhiên, bạn sẽ cần phải tìm một cách để ngẫu nhiên hạt giống! Và bất kỳ chiến lược nào bạn đã sử dụng chắc chắn sẽ không thuần túy, bởi vì nó sẽ yêu cầu bạn thu thập thông tin từ một nguồn bên ngoài chức năng của bạn. Như mtraceurjpmc26 nhắc nhở tôi, điều này bao gồm tất cả các phương pháp tiếp cận vật lý: bộ tạo số ngẫu nhiên phần cứng , webcam có nắp thấu kính , bộ thu nhiễu khí quyển - thậm chí cả đèn dung nham . Tất cả những điều này liên quan đến việc sử dụng dữ liệu được tính toán và lưu trữ bên ngoài hàm.


8
Math.random () không chỉ đọc "hạt giống" của nó mà còn sửa đổi nó, để cuộc gọi tiếp theo sẽ trả về một cái gì đó khác. Tùy thuộc vào và sửa đổi, trạng thái tĩnh chắc chắn không tốt cho một hàm thuần túy.
Nate Eldredge

2
@NateEldredge, khá như vậy! Mặc dù chỉ cần đọc một giá trị phụ thuộc vào triển khai là đủ để phá vỡ độ tinh khiết. Ví dụ: bạn có bao giờ để ý rằng hàm băm Python 3 không ổn định giữa các quy trình như thế nào không?
gửi

2
Câu trả lời này sẽ thay đổi như Math.randomthế nào nếu không sử dụng PRNG mà thay vào đó được thực hiện bằng RNG phần cứng? Phần cứng RNG không thực sự có trạng thái theo nghĩa thông thường, nhưng nó tạo ra các giá trị ngẫu nhiên (và do đó đầu ra chức năng vẫn khác nhau bất kể đầu vào), phải không?
mtraceur

@mtraceur, đúng vậy. Nhưng tôi không nghĩ câu trả lời sẽ thay đổi nhiều. Trên thực tế, đây là lý do tại sao tôi không dành thời gian nói về "trạng thái" trong câu trả lời của mình. Đọc từ RNG phần cứng cũng có nghĩa là đọc từ "dữ liệu được tính toán và lưu trữ ở nơi khác." Nó chỉ là dữ liệu được tính toán và lưu trữ trong môi trường vật lý của chính máy tính khi nó tương tác với môi trường của nó.
gửi

1
Logic tương tự này thậm chí còn áp dụng cho các sơ đồ ngẫu nhiên phức tạp hơn, ngay cả những sơ đồ như nhiễu khí quyển của Random.org . +1
jpmc26

38

Một hàm thuần túy là một hàm trong đó giá trị trả về chỉ được xác định bởi các giá trị đầu vào của nó, không có tác dụng phụ có thể quan sát được

Bằng cách sử dụng Math.random, bạn đang xác định giá trị của nó bằng một thứ gì đó khác với giá trị đầu vào. Nó không phải là một chức năng thuần túy.

nguồn


25

Không, nó không phải là một hàm thuần túy vì đầu ra của nó không chỉ phụ thuộc vào đầu vào được cung cấp (Math.random () có thể xuất ra bất kỳ giá trị nào), trong khi các hàm thuần túy phải luôn xuất ra cùng một giá trị cho các đầu vào giống nhau.

Nếu một hàm thuần túy, sẽ an toàn khi tối ưu hóa nhiều cuộc gọi với cùng một đầu vào và chỉ sử dụng lại kết quả của một cuộc gọi trước đó.

PS đối với tôi ít nhất và với nhiều người khác, redux đã làm cho thuật ngữ thuần chức năng trở nên phổ biến. Trực tiếp từ tài liệu redux :

Những điều bạn không bao giờ nên làm bên trong một bộ giảm tốc:

  • Thay đổi các lập luận của nó;

  • Thực hiện các tác dụng phụ như lệnh gọi API và chuyển đổi định tuyến;

  • Gọi các hàm không thuần túy, ví dụ: Date.now () hoặc Math.random ().


3
Mặc dù những người khác đã cung cấp câu trả lời tuyệt vời, nhưng tôi không thể cưỡng lại bản thân mình khi Redux doc của lóe lên trong óc tôi và Math.random () được nêu cụ thể trong đó :)
Shubhnik Singh

20

Từ quan điểm toán học, chữ ký của bạn không

test: <number, number> -> <number>

nhưng

test: <environment, number, number> -> <environment, number>

nơi environmentcó khả năng cung cấp kết quả của Math.random(). Và thực sự tạo ra giá trị ngẫu nhiên làm thay đổi môi trường như một hiệu ứng phụ, vì vậy bạn cũng trả lại một môi trường mới, không bằng môi trường đầu tiên!

Nói cách khác, nếu bạn cần bất kỳ loại đầu vào nào không đến từ các đối số ban đầu ( <number, number>phần), thì bạn cần được cung cấp môi trường thực thi (trong ví dụ này cung cấp trạng thái cho Math). Điều tương tự cũng áp dụng cho những thứ khác được đề cập bởi các câu trả lời khác, như I / O hoặc tương tự.


Như một phép tương tự, bạn cũng có thể nhận thấy đây là cách lập trình hướng đối tượng có thể được biểu diễn - nếu chúng ta nói, ví dụ:

SomeClass something
T result = something.foo(x, y)

thì thực sự chúng tôi đang sử dụng

foo: <something: SomeClass, x: Object, y: Object> -> <SomeClass, T>

với đối tượng mà phương thức của nó được gọi là một phần của môi trường. Và tại sao SomeClassmột phần của kết quả? Bởi vì somethingtrạng thái của cũng có thể thay đổi!


7
Tệ hơn nữa, môi trường cũng bị đột biến, vì vậy test: <environment, number, number> -> <environment, number>nó phải như vậy
Bergi

1
Tôi không chắc ví dụ OO rất giống nhau. a.F(b, c)có thể được coi là đường cú pháp F(a, b, c)với một quy tắc đặc biệt để gửi đến các định nghĩa quá tải Fdựa trên kiểu của a(đây thực sự là cách Python biểu diễn nó). Nhưng avẫn rõ ràng trong cả hai ký hiệu, trong khi môi trường trong một hàm không thuần túy không bao giờ được đề cập trong mã nguồn.
IMSoP


10

Ngoài các câu trả lời khác chỉ ra một cách chính xác cách hàm này không xác định, nó cũng có một tác dụng phụ: nó sẽ khiến các lệnh gọi trong tương lai math.random()trả về một câu trả lời khác. Và trình tạo số ngẫu nhiên không có thuộc tính đó thường sẽ thực hiện một số loại I / O, chẳng hạn như để đọc từ một thiết bị ngẫu nhiên được cung cấp bởi Hệ điều hành. Hoặc là chi tiết cho một hàm thuần túy.


7

Không, không phải vậy. Bạn không thể tìm ra kết quả, vì vậy không thể kiểm tra đoạn mã này. Để làm cho mã đó có thể kiểm tra được, bạn cần trích xuất thành phần tạo ra số ngẫu nhiên:

function test(min, max, generator) {
  return  generator() * (max - min) + min;
}

Bây giờ, bạn có thể giả lập trình tạo và kiểm tra mã của mình đúng cách:

const result = test(1, 2, () => 3);
result == 4 //always true

Và trong mã "sản xuất" của bạn:

const result = test(1, 2, Math.random);

1
▲ cho suy nghĩ của bạn về khả năng kiểm tra. Với một chút cẩn thận, bạn cũng có thể tạo ra các bài kiểm tra lặp lại trong khi chấp nhận một util.Random, bạn có thể bắt đầu chạy thử nghiệm khi bắt đầu chạy thử nghiệm để lặp lại hành vi cũ hoặc cho một lần chạy mới (nhưng có thể lặp lại). Nếu đa luồng, bạn có thể thực hiện việc này trong luồng chính và sử dụng nó Randomđể gieo chuỗi cục bộ có thể lặp lại Random. Tuy nhiên, theo tôi hiểu, nó test(int,int,Random)không được coi là tinh khiết vì nó làm thay đổi trạng thái của Random.
PJTraill

2

Bạn sẽ ổn với những điều sau:

return ("" + test(0,1)) + test(0,1);

tương đương với

var temp = test(0, 1);
return ("" + temp) + temp;

?

Bạn thấy đấy, định nghĩa thuần túy là một hàm mà đầu ra của nó không thay đổi với bất kỳ thứ gì khác ngoài đầu vào của nó. Nếu chúng tôi nói rằng JavaScript có cách để gắn thẻ một hàm thuần túy và tận dụng lợi thế này, trình tối ưu hóa sẽ được phép viết lại biểu thức đầu tiên dưới dạng biểu thức thứ hai.

Tôi có kinh nghiệm thực tế về điều này. Máy chủ SQL được phép getdate()newid()trong các chức năng "thuần túy" và trình tối ưu hóa sẽ loại bỏ các lệnh gọi theo ý muốn. Đôi khi điều này sẽ làm một cái gì đó ngu ngốc.

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.