Nhìn qua Haskell Prelude, tôi thấy một chức năng const
:
const x _ = x
Tôi dường như không thể tìm thấy bất cứ điều gì liên quan đến chức năng này.
Vấn đề ở đây là gì? Bất cứ ai có thể cho một ví dụ về nơi chức năng này có thể được sử dụng?
Nhìn qua Haskell Prelude, tôi thấy một chức năng const
:
const x _ = x
Tôi dường như không thể tìm thấy bất cứ điều gì liên quan đến chức năng này.
Vấn đề ở đây là gì? Bất cứ ai có thể cho một ví dụ về nơi chức năng này có thể được sử dụng?
Câu trả lời:
Nó hữu ích để chuyển đến các hàm bậc cao hơn khi bạn không cần tất cả sự linh hoạt của chúng. Ví dụ: toán tử trình tự đơn nguyên >>
có thể được định nghĩa theo toán tử liên kết đơn nguyên là
x >> y = x >>= const y
Nó có phần gọn gàng hơn so với sử dụng lambda
x >> y = x >>= \_ -> y
và bạn thậm chí có thể sử dụng miễn phí
(>>) = (. const) . (>>=)
mặc dù tôi không đặc biệt khuyên bạn điều đó trong trường hợp này.
map (const 42) [1..5]
kết quả là [42, 42, 42, 42, 42]
.
const
rất hữu ích cho việc áp dụng cho một đối số duy nhất để mang lại một hàm khi cần một hàm (chẳng hạn như chuyển tới map
).
head = foldr const (error "Prelude.head: empty list")
Để thêm vào câu trả lời trực tiếp tuyệt vời của hammar: các hàm khiêm tốn như const
và id
thực sự hữu ích như một hàm bậc cao hơn vì cùng một lý do là chúng là cơ bản trong phép tính tổ hợp SKI .
Tôi không nghĩ rằng các chức năng dạo đầu của haskell được mô hình hóa một cách có ý thức theo hệ thống chính thức đó hay bất cứ thứ gì. Chỉ là việc tạo ra những thứ trừu tượng phong phú trong haskell là rất dễ dàng, vì vậy bạn thường thấy những thứ lý thuyết này trở nên hữu ích trong thực tế.
Trình cắm không biết xấu hổ, nhưng tôi đã viết blog về cách phiên bản Ứng dụng (->)
thực sự là S
và K
tổ hợp ở đây , nếu đó là loại điều bạn yêu thích.
((->) e)
cũng là đơn nguyên của người đọc - với Reader
và tương tự chỉ là newtype
trình bao bọc - và ask
sau đó là chức năng id
, vì vậy đó cũng là trình I
tổ hợp. Nếu bạn nhìn thay vì tại cơ sở BCKW gốc Haskell Curry của, B
, K
, và W
là fmap
, return
và join
tương ứng.
Một ví dụ đơn giản để sử dụng const
là Data.Functor.(<$)
. Với chức năng này, bạn có thể nói: Tôi có ở đây một cái functor với thứ gì đó nhàm chán trong đó, nhưng thay vào đó tôi muốn có thứ thú vị khác trong đó mà không làm thay đổi hình dạng của cái functor. Ví dụ
import Data.Functor
42 <$ Just "boring"
--> Just 42
42 <$ Nothing
--> Nothing
"cool" <$ ["nonsense","stupid","uninteresting"]
--> ["cool","cool","cool"]
Định nghĩa là:
(<$) :: a -> f b -> f a
(<$) = fmap . const
hoặc viết không phải là vô nghĩa:
cool <$ uncool = fmap (const cool) uncool
Bạn xem cách const
được sử dụng ở đây để "quên" đầu vào.
Tôi dường như không thể tìm thấy bất cứ điều gì liên quan đến chức năng này.
Nhiều câu trả lời khác thảo luận về các ứng dụng tương đối bí truyền (ít nhất là đối với người mới) của const
. Đây là một đơn giản: bạn có thể sử dụngconst
để loại bỏ lambda có hai đối số, loại bỏ đối số đầu tiên nhưng làm điều gì đó thú vị với đối số thứ hai.
Ví dụ, cách triển khai (không hiệu quả nhưng mang tính hướng dẫn) sau đây length
,
length' = foldr (\_ acc -> 1 + acc) 0
có thể được viết lại thành
length' = foldr (const (1+)) 0
mà có lẽ là thanh lịch hơn.
Biểu thức const (1+)
thực sự tương đương về mặt ngữ nghĩa \_ acc -> 1 + acc
, bởi vì nó lấy một đối số, ném nó đi và trả về phần (1+)
.
Một cách sử dụng khác là triển khai các hàm thành viên lớp có đối số giả không được đánh giá (được sử dụng để giải quyết các kiểu không rõ ràng). Ví dụ có thể có trong Data.bits:
instance Bits Int where
isSigned = const True
bitSize = const wordSize
...
Bằng cách sử dụng const, chúng tôi nói rõ ràng rằng chúng tôi đang xác định các giá trị không đổi.
Cá nhân tôi không thích việc sử dụng các tham số giả, nhưng nếu chúng được sử dụng trong một lớp thì đây là một cách khá hay để viết các phiên bản.
const
có thể chỉ là phần triển khai bạn đang tìm kiếm kết hợp với các chức năng khác. Đây là một ví dụ tôi đã phát hiện ra.
Giả sử chúng ta muốn viết lại một cấu trúc gồm 2 bộ dữ liệu thành một cấu trúc 2 bộ dữ liệu khác. Tôi có thể diễn đạt điều này như vậy:
((a,b),(c,d)) ⇒ (a,(c,(5,a)))
Tôi có thể đưa ra một định nghĩa rõ ràng về đối sánh mẫu:
f ((a,b),(c,d)) = (a,(c,(5,a)))
Điều gì sẽ xảy ra nếu tôi muốn một giải pháp vô nghĩa (ngầm) cho những loại viết lại này? Một số suy nghĩ và tìm kiếm sau đó, câu trả lời là chúng ta có thể diễn đạt bất kỳ đoạn viết lại nào (&&&), const, (.), fst, snd
. Lưu ý rằng (&&&)
từControl.Arrow
.
Giải pháp của ví dụ sử dụng các hàm này là:
(fst.fst &&& (fst.snd &&& (const 5 &&& fst.fst)))
Lưu ý sự tương tự với (a,(c,(5,a)))
. Nếu chúng ta thay thế &&&
bằng ,
? Sau đó, nó đọc:
(fst.fst, (fst.snd, (const 5, fst.fst)))
Chú ý a
phần tử đầu tiên của phần tử đầu tiên như thế nào , và đó là những gì fst.fst
dự án. Chú ý c
phần tử đầu tiên của phần tử thứ hai như thế nào và đó là những gì fst.snd
dự án. Đó là, các biến trở thành đường dẫn đến nguồn của chúng.
const
cho phép chúng tôi giới thiệu các hằng số. Thật thú vị làm thế nào mà cái tên đi kèm với ý nghĩa!
Sau đó tôi khái quát hóa ý tưởng này với applicative để bạn có thể viết bất kỳ chức năng trong một phong cách vô nghĩa (vì vậy miễn là bạn có phân tích trường hợp có sẵn như là chức năng, chẳng hạn như maybe
, either
, bool
). Một lần nữa, const
đóng vai trò giới thiệu các hằng số. Bạn có thể thấy công việc này trong Data.Function.Tacit gói .
Khi bạn bắt đầu một cách trừu tượng, ở mục tiêu, và sau đó hướng tới việc thực hiện, bạn có thể ngạc nhiên trước câu trả lời. Có nghĩa là, bất kỳ một chức năng nào cũng có thể bí ẩn như bất kỳ một bánh răng nào trong máy. Tuy nhiên, nếu bạn lùi lại để đưa toàn bộ máy vào tầm nhìn, bạn có thể hiểu được bối cảnh mà bánh răng đó là cần thiết.
Giả sử bạn muốn tạo một danh sách Nothings
bằng độ dài của một chuỗi. Khi const
trả về đối số đầu tiên của nó, bất kể đối số thứ hai, bạn có thể làm:
listOfNothings :: String -> [Maybe Char]
listOfNothings = (map . const) Nothing
hoặc, rõ ràng hơn:
listOfNothing st = map (const Nothing) st
Giả sử bạn muốn xoay một danh sách. Đây là một cách thành ngữ để làm như vậy trong Haskell:
rotate :: Int -> [a] -> [a]
rotate _ [] = []
rotate n xs = zipWith const (drop n (cycle xs)) xs
Hàm này nén hai mảng với hàm const
, mảng đầu tiên là mảng tuần hoàn vô hạn, mảng thứ hai là mảng bạn đã bắt đầu.
const
hoạt động như kiểm tra giới hạn và sử dụng mảng ban đầu để kết thúc mảng tuần hoàn.
Tôi dường như không thể tìm thấy bất cứ điều gì liên quan đến chức năng này.
Giả sử bạn muốn tạo tất cả các dãy con của một danh sách nhất định.
Đối với mỗi phần tử danh sách, tại một thời điểm nhất định, bạn có lựa chọn Đúng (bao gồm nó trong dãy con hiện tại) hoặc Sai (không bao gồm nó). Điều này có thể được thực hiện bằng cách sử dụng chức năng filterM .
Như thế này:
λ> import Control.Monad
λ> :t filterM
filterM :: Applicative m => (a -> m Bool) -> [a] -> m [a]
λ>
Ví dụ, chúng tôi muốn tất cả các dãy con của [1..4]
.
λ> filterM (const [True, False]) [1..4]
[[1,2,3,4],[1,2,3],[1,2,4],[1,2],[1,3,4],[1,3],[1,4],[1],[2,3,4],[2,3],[2,4],[2],[3,4],[3],[4],[]]
λ>
backgroundColor :: Text -> Color
đối với tôibackgroundColor = const White