हास्केल में शुद्ध कार्यों और दुष्प्रभावों को समझना - putStrLn


10

हाल ही में, मैंने हास्केल सीखना शुरू कर दिया क्योंकि मैं कार्यात्मक प्रोग्रामिंग पर अपने ज्ञान को व्यापक बनाना चाहता था और मुझे कहना होगा कि मैं वास्तव में अब तक इसे प्यार कर रहा हूं। वर्तमान में मैं जिस संसाधन का उपयोग कर रहा हूं, वह प्लुरलसाइट पर 'हास्केल फंडामेंटल पार्ट 1' है। दुर्भाग्य से मुझे निम्नलिखित कोड के बारे में व्याख्याता के एक विशेष उद्धरण को समझने में कुछ कठिनाई हो रही है और उम्मीद कर रहा था कि आप लोग इस विषय पर कुछ प्रकाश डाल सकते हैं।

आचार संहिता

helloWorld :: IO ()
helloWorld = putStrLn "Hello World"

main :: IO ()
main = do
    helloWorld
    helloWorld
    helloWorld

उद्धरण

यदि आपके पास एक ही ब्लॉक में कई बार एक ही IO कार्रवाई है, तो इसे कई बार चलाया जाएगा। इसलिए यह कार्यक्रम तीन बार 'हेलो वर्ल्ड' स्ट्रिंग को प्रिंट करता है। यह उदाहरण वर्णन करने में मदद करता है कि putStrLnसाइड इफेक्ट्स के साथ एक फ़ंक्शन नहीं है। हम चर putStrLnको परिभाषित करने के लिए एक बार फ़ंक्शन कहते हैं helloWorld। यदि putStrLnस्ट्रिंग को प्रिंट करने का कोई साइड इफेक्ट होता है, तो यह केवल एक बार प्रिंट करेगा और helloWorldमुख्य डू-ब्लॉक में दोहराए गए चर का कोई प्रभाव नहीं होगा।

अधिकांश अन्य प्रोग्रामिंग भाषाओं में, इस तरह का एक कार्यक्रम केवल एक बार 'हैलो वर्ल्ड' को प्रिंट करेगा, क्योंकि मुद्रण तब होगा जब putStrLnफ़ंक्शन को बुलाया जाएगा। यह सूक्ष्म अंतर अक्सर शुरुआती लोगों की यात्रा करेगा, इसलिए इस बारे में थोड़ा सोचें, और यह सुनिश्चित करें कि आप समझें कि यह कार्यक्रम 'हैलो वर्ल्ड' को तीन बार प्रिंट क्यों करता है और क्यों यह केवल एक बार प्रिंट करेगा यदि putStrLnफ़ंक्शन ने साइड इफेक्ट के रूप में मुद्रण किया।

जो मुझे समझ नहीं आ रहा है

मेरे लिए यह लगभग स्वाभाविक है कि स्ट्रिंग 'हैलो वर्ल्ड' तीन बार छपी है। मैं helloWorldचर (या फ़ंक्शन?) को कॉलबैक के एक प्रकार के रूप में देखता हूं जो बाद में आह्वान किया जाता है। जो मुझे समझ नहीं आ रहा है, putStrLnउसका साइड इफ़ेक्ट कैसे हुआ , इसका परिणाम यह होगा कि स्ट्रिंग केवल एक बार छपी होगी। या क्यों यह केवल एक बार अन्य प्रोग्रामिंग भाषाओं में मुद्रित किया जाएगा।

चलो सी # कोड में कहते हैं, मुझे लगता है कि यह इस तरह दिखेगा:

सी # (फिडल)

using System;

public class Program
{
    public static void HelloWorld()
    {
        Console.WriteLine("Hello World");
    }

    public static void Main()
    {
        HelloWorld();
        HelloWorld();
        HelloWorld();
    }
}

मुझे यकीन है कि मैं कुछ सरल या गलत तरीके से उनकी शब्दावली की अनदेखी कर रहा हूं। किसी भी तरह की सहायता का स्वागत किया जाएगा।

संपादित करें:

आपके जवाबों के लिए आप सभी का धन्यवाद! आपके उत्तरों से मुझे इन अवधारणाओं की बेहतर समझ प्राप्त करने में मदद मिली। मुझे नहीं लगता कि यह पूरी तरह से अभी तक क्लिक किया गया है, लेकिन मैं भविष्य में इस विषय पर फिर से विचार करूंगा, धन्यवाद!


2
helloWorldC # में किसी फ़ील्ड या वैरिएबल की तरह स्थिर रहने के बारे में सोचें । कोई पैरामीटर नहीं है जिसे लागू किया जा रहा है helloWorld
कार्मिरियल

2
putStrLn एक साइड इफेक्ट नहीं है; यह केवल एक IO कार्रवाई लौटाता है, तर्क के लिए एक ही IO क्रिया "Hello World"चाहे आप कितनी बार कॉल करें putStrLn
चेपनर

1
अगर ऐसा किया, helloworldतो एक कार्रवाई नहीं होगी जो प्रिंट करती है Hello world; यह द्वारा दिए गए मान होगा putStrLn के बाद यह मुद्रित Hello World(यानी, ())।
चेपनर

2
मुझे लगता है कि इस उदाहरण को समझने के लिए आपको पहले से ही समझना होगा कि हास्केल में साइड इफेक्ट्स कैसे काम करते हैं। यह एक अच्छा उदाहरण नहीं है।
18:25 बजे user253751

अपने C # स्निपेट में आप ऐसा नहीं करते helloWorld = Console.WriteLine("Hello World");। आप बस फंक्शन Console.WriteLine("Hello World");में सम्‍मिलित किए जाने वाले HelloWorldफ़ंक्शन को सम्‍मिलित करते हैं HelloWorld। अब सोचिए कि क्या helloWorld = putStrLn "Hello World"बनाता है helloWorld। यह एक IO सन्यासी को सौंपा जाता है जिसमें शामिल होता है ()। एक बार जब आप इसे बांधते हैं तो >>=यह केवल गतिविधि (कुछ मुद्रण) करता है और आपको ()बाइंड ऑपरेटर के दाईं ओर देगा।
Redu

जवाबों:


8

यह समझना आसान होगा कि लेखक का मतलब क्या है अगर हम helloWorldएक स्थानीय चर के रूप में परिभाषित करते हैं :

main :: IO ()
main = do
  let helloWorld = putStrLn "Hello World!"
  helloWorld
  helloWorld
  helloWorld

जो आप इस सी # की तरह pseudocode से तुलना कर सकते हैं:

void Main() {
  var helloWorld = {
    WriteLine("Hello World!")
  }
  helloWorld;
  helloWorld;
  helloWorld;
}

Ie in C # WriteLineएक ऐसी प्रक्रिया है जो अपने तर्क को छापती है और कुछ भी नहीं लौटाती है। हास्केल में, putStrLnएक फ़ंक्शन है जो एक स्ट्रिंग लेता है और आपको एक एक्शन देता है जो उस स्ट्रिंग को प्रिंट करेगा जो इसे निष्पादित किया जाना था। इसका मतलब है कि लेखन में बिल्कुल कोई अंतर नहीं है

do
  let hello = putStrLn "Hello World"
  hello
  hello

तथा

do
  putStrLn "Hello World"
  putStrLn "Hello World"

यह कहा जा रहा है, इस उदाहरण में अंतर विशेष रूप से गहरा नहीं है, इसलिए यह ठीक है यदि आपको वह नहीं मिलता है जो लेखक इस खंड में पाने की कोशिश कर रहा है और अभी के लिए आगे बढ़ रहा है।

यह थोड़ा बेहतर काम करता है यदि आप इसकी तुलना अजगर से करते हैं

hello_world = print('hello world')
hello_world
hello_world
hello_world

यहां मुद्दा यह जा रहा है कि हास्केल में आईओ कार्रवाई 'असली' मूल्यों कि आगे "कॉलबैक" या एक तरह से कुछ भी में लिपटे होने की जरूरत नहीं है उन्हें क्रियान्वित करने से रोकने के लिए कर रहे हैं - बल्कि, एक ही रास्ता करने के लिए करना है निष्पादित करने के लिए उन्हें प्राप्त उन्हें एक विशेष स्थान पर रखने के लिए (यानी कहीं अंदर mainया एक धागा बंद हो गया main)।

यह सिर्फ एक पार्लर ट्रिक नहीं है, इससे अंत में कुछ दिलचस्प प्रभाव पड़ते हैं कि आप कोड कैसे लिखते हैं (उदाहरण के लिए, यह इस कारण का हिस्सा है कि हास्केल को वास्तव में आपके द्वारा परिचित किसी भी सामान्य नियंत्रण संरचना की आवश्यकता नहीं है! अनिवार्य भाषाओं के साथ और इसके बजाय कार्यों के मामले में सब कुछ करने के साथ दूर हो सकता है), लेकिन फिर से मैं इस बारे में बहुत ज्यादा चिंता नहीं करूंगा (इन जैसे एनालॉग्स हमेशा तुरंत क्लिक नहीं करते हैं)


4

वर्णित अंतर को देखना आसान हो सकता है यदि आप किसी ऐसे फ़ंक्शन का उपयोग करते हैं जो वास्तव में कुछ करता है, बजाय helloWorld। निम्नलिखित के बारे में सोचो:

add :: Int -> Int -> IO Int
add x y = do
  putStrLn ("I am adding " ++ show x ++ " and " ++ show y)
  return (x + y)

plus23 :: IO Int
plus23 = add 2 3

main :: IO ()
main = do
  _ <- plus23
  _ <- plus23
  _ <- plus23
  return ()

यह "मैं 2 और 3 जोड़ रहा हूं" 3 बार प्रिंट होगा।

C # में, आप निम्नलिखित लिख सकते हैं:

using System;

public class Program
{
    public static int add(int x, int y)
    {
        Console.WriteLine("I am adding {0} and {1}", x, y);
        return x + y;
    }

    public static void Main()
    {
        int x;
        int plus23 = add(2, 3);
        x = plus23;
        x = plus23;
        x = plus23;
        return;
    }
}

जो केवल एक बार छपेगा।


3

यदि मूल्यांकन के putStrLn "Hello World"दुष्प्रभाव होते हैं, तो फिर संदेश केवल एक बार मुद्रित किया जाएगा।

हम निम्नलिखित कोड के साथ उस परिदृश्य का अनुमान लगा सकते हैं:

import System.IO.Unsafe (unsafePerformIO)
import Control.Exception (evaluate)

helloWorld :: ()
helloWorld = unsafePerformIO $ putStrLn "Hello World"

main :: IO ()
main = do
    evaluate helloWorld
    evaluate helloWorld
    evaluate helloWorld

unsafePerformIOएक IOकार्रवाई करता है और "भूल जाता है" यह एक IOकार्रवाई है, इसे IOक्रियाओं की संरचना द्वारा लगाए गए सामान्य अनुक्रमण से अनमूर किया जाता है और आलसी मूल्यांकन की योनि के अनुसार प्रभाव (या नहीं) लेने देता है।

evaluateएक शुद्ध मूल्य लेता है और यह सुनिश्चित करता है कि जब भी परिणामस्वरूप IOकार्रवाई का मूल्यांकन किया जाता है, तो मूल्य का मूल्यांकन किया जाता है - जो हमारे लिए यह होगा, क्योंकि यह पथ में निहित है main। हम इसका उपयोग कुछ मूल्यों के मूल्यांकन को कार्यक्रम के अंत में जोड़ने के लिए कर रहे हैं।

यह कोड केवल एक बार "हैलो वर्ल्ड" प्रिंट करता है। हम helloWorldएक शुद्ध मूल्य के रूप में मानते हैं । लेकिन इसका मतलब है कि यह सभी evaluate helloWorldकॉल के बीच साझा किया जाएगा । और क्यों नहीं? यह सब के बाद एक शुद्ध मूल्य है, क्यों यह अनावश्यक रूप से पुनर्गणना करता है? पहली evaluateक्रिया "छिपे हुए" प्रभाव को "पॉप" करती है और बाद की क्रियाएं केवल परिणामी का मूल्यांकन करती हैं (), जो आगे किसी भी प्रभाव का कारण नहीं बनती हैं।


1
यह ध्यान देने योग्य है कि आपको unsafePerformIOहास्केल सीखने के इस चरण में बिल्कुल उपयोग नहीं करना चाहिए । यह एक कारण के लिए नाम में "असुरक्षित" है, और आपको इसका उपयोग तब तक नहीं करना चाहिए जब तक आप कर सकते हैं (और किया) संदर्भ में इसके उपयोग के निहितार्थों पर ध्यान से विचार करें। कोड है कि danidiaz जवाब में पूरी तरह से unintuitive व्यवहार का परिणाम है कि से कब्जा कर सकते हैं unsafePerformIO
एंड्रयू रे

1

नोटिस करने के लिए एक विवरण है: आप putStrLnकेवल एक बार फ़ंक्शन को परिभाषित करते हुए कहते हैं helloWorld। में mainसमारोह आप सिर्फ इतना है कि की वापसी मान का उपयोग putStrLn "Hello, World"तीन बार।

व्याख्याता का कहना है कि putStrLnकॉल का कोई दुष्प्रभाव नहीं है और यह सच है। लेकिन इसके प्रकार को देखें helloWorld- यह एक IO क्रिया है। putStrLnबस इसे आप के लिए बनाता है। बाद में, आप उनमें से 3 को doएक और IO कार्रवाई बनाने के लिए ब्लॉक के साथ श्रृंखला करते हैं - main। बाद में, जब आप अपने प्रोग्राम को निष्पादित करते हैं, तो वह क्रिया चल जाएगी, यही वह जगह है जहाँ साइड इफेक्ट्स झूठ बोलते हैं।

तंत्र जो इसके आधार में निहित है - भिक्षु । यह शक्तिशाली अवधारणा आपको किसी भाषा में मुद्रण जैसे कुछ साइड इफेक्ट्स का उपयोग करने की अनुमति देती है जो सीधे साइड इफेक्ट का समर्थन नहीं करती है। आप बस कुछ क्रियाओं को चेन करते हैं, और वह चेन आपके प्रोग्राम के शुरू होने पर चलाया जाएगा। आपको उस अवधारणा को गहराई से समझने की आवश्यकता होगी यदि आप हास्केल का गंभीरता से उपयोग करना चाहते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.