পুনরাবৃত্তাকার যোগফলের ধরণের সাথে ডিল করার সময় কীভাবে কোড সদৃশতা হ্রাস করা যায়


50

আমি বর্তমানে একটি প্রোগ্রামিং ভাষার জন্য একটি সাধারণ দোভাষী নিয়ে কাজ করছি এবং আমার কাছে এই জাতীয় ডেটা টাইপ রয়েছে:

data Expr
  = Variable String
  | Number Int
  | Add [Expr]
  | Sub Expr Expr

এবং আমার অনেকগুলি ফাংশন রয়েছে যা সাধারণ জিনিসগুলি করে:

-- Substitute a value for a variable
substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue = go
  where
    go (Variable x)
      | x == name = Number newValue
    go (Add xs) =
      Add $ map go xs
    go (Sub x y) =
      Sub (go x) (go y)
    go other = other

-- Replace subtraction with a constant with addition by a negative number
replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd = go
  where
    go (Sub x (Number y)) =
      Add [go x, Number (-y)]
    go (Add xs) =
      Add $ map go xs
    go (Sub x y) =
      Sub (go x) (go y)
    go other = other

কিন্তু এই ফাংশনগুলির প্রত্যেকটিতে, আমাকে সেই অংশটি পুনরাবৃত্তি করতে হবে যা ফাংশনের একটি অংশে সামান্য পরিবর্তন করে কোডটিকে পুনরাবৃত্তভাবে ডাকে calls আরও সাধারণভাবে এটি করার কোনও বিদ্যমান উপায় আছে কি? আমাকে বরং এই অংশটি অনুলিপি করতে হবে না:

    go (Add xs) =
      Add $ map go xs
    go (Sub x y) =
      Sub (go x) (go y)
    go other = other

এবং প্রতিবার কেবল একটি একক কেস পরিবর্তন করুন কারণ এ জাতীয় কোডটির সদৃশ করতে এটি অক্ষম বলে মনে হয়।

আমি যে সমাধানটি সামনে আসতে পারলাম তা হ'ল একটি ফাংশন যা পুরো ডেটা স্ট্রাকচারে প্রথমে একটি ফাংশন কল করে এবং তারপরে পুনরাবৃত্তির সাথে ফলাফলের মতো করে:

recurseAfter :: (Expr -> Expr) -> Expr -> Expr
recurseAfter f x =
  case f x of
    Add xs ->
      Add $ map (recurseAfter f) xs
    Sub x y ->
      Sub (recurseAfter f x) (recurseAfter f y)
    other -> other

substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue =
  recurseAfter $ \case
    Variable x
      | x == name -> Number newValue
    other -> other

replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd =
  recurseAfter $ \case
    Sub x (Number y) ->
      Add [x, Number (-y)]
    other -> other

তবে আমি মনে করি এটির আগে থেকে ইতিমধ্যে করার একটি সহজ উপায় থাকতে হবে। আমি কিছু অনুপস্থিত করছি?


কোডটির একটি "উত্তোলন" সংস্করণ তৈরি করুন। আপনি যেখানে প্যারামিটারগুলি (ফাংশন) ব্যবহার করেন তা স্থির করে যা করা উচিত। তারপরে আপনি উত্তোলিত সংস্করণে ফাংশনগুলি পাস করে নির্দিষ্ট ফাংশন তৈরি করতে পারেন।
উইলেম ভ্যান অনসেম

আমি মনে করি আপনার ভাষা সরল করা যেতে পারে। Add :: Expr -> Expr -> Exprপরিবর্তে সংজ্ঞা দিন Add :: [Expr] -> Exprএবং Subপুরোপুরি পরিত্রাণ পান ।
চিপনার

আমি কেবল এই সংজ্ঞাটিকে সরলীকৃত সংস্করণ হিসাবে ব্যবহার করছি; যদিও এই ক্ষেত্রে এটি কাজ করবে, আমার পাশাপাশি ভাষার অন্যান্য অংশের জন্য মত প্রকাশের তালিকা থাকতে সক্ষম হতে হবে
স্কট

যেমন? বেশিরভাগ, সমস্ত না থাকলে শিকলযুক্ত অপারেটরগুলি নেস্টেড বাইনারি অপারেটরগুলিতে হ্রাস করা যেতে পারে।
চিপনার

1
আমি মনে করি আপনার recurseAfterহয় anaছদ্মবেশে। আপনি অ্যানোরমিজমগুলি এবং দেখতে চাইতে পারেন recursion-schemes। বলা হচ্ছে, আমি মনে করি আপনার চূড়ান্ত সমাধানটি যতটা সংক্ষিপ্ত হতে পারে। অফিশিয়াল recursion-schemesঅ্যানোমার্ফিজমগুলিতে স্যুইচ করা বেশি কিছু বাঁচবে না।
চি

উত্তর:


38

অভিনন্দন, আপনি কেবল অ্যানোমার্ফিজমগুলি আবিষ্কার করেছেন!

আপনার কোডটি এখানে পুনরায় রঙ করা হয়েছে যাতে এটি recursion-schemesপ্যাকেজের সাথে কাজ করে । হায়, এটি সংক্ষিপ্ত নয়, যেহেতু যন্ত্রপাতিটির কাজ করতে আমাদের কিছু বয়লারপ্লেট প্রয়োজন। (বয়লারপ্লেট এড়াতে কিছু অটোমেজিক উপায় থাকতে পারে, যেমন জেনেরিক ব্যবহার করা I আমি কেবল জানি না))

নীচে, আপনার recurseAfterমান সহ প্রতিস্থাপন করা হয় ana

আমরা প্রথমে আপনার পুনরাবৃত্ত প্রকারটি সংজ্ঞায়িত করি, পাশাপাশি ফান্টারের এটির স্থির বিন্দু।

{-# LANGUAGE DeriveFunctor, TypeFamilies, LambdaCase #-}
{-# OPTIONS -Wall #-}
module AnaExpr where

import Data.Functor.Foldable

data Expr
  = Variable String
  | Number Int
  | Add [Expr]
  | Sub Expr Expr
  deriving (Show)

data ExprF a
  = VariableF String
  | NumberF Int
  | AddF [a]
  | SubF a a
  deriving (Functor)

তারপরে আমরা দু'টিকে কয়েকটি দৃষ্টান্তের সাথে সংযুক্ত করি যাতে আমরা আইসোমর্ফিকের Exprমধ্যে প্রকাশ পেতে পারি ExprF Exprএবং এটি আবার ভাঁজ করতে পারি।

type instance Base Expr = ExprF
instance Recursive Expr where
   project (Variable s) = VariableF s
   project (Number i) = NumberF i
   project (Add es) = AddF es
   project (Sub e1 e2) = SubF e1 e2
instance Corecursive Expr where
   embed (VariableF s) = Variable s
   embed (NumberF i) = Number i
   embed (AddF es) = Add es
   embed (SubF e1 e2) = Sub e1 e2

অবশেষে, আমরা আপনার মূল কোডটি মানিয়ে নিয়েছি এবং কয়েকটি পরীক্ষা যুক্ত করি।

substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue = ana $ \case
    Variable x | x == name -> NumberF newValue
    other                  -> project other

testSub :: Expr
testSub = substituteName "x" 42 (Add [Add [Variable "x"], Number 0])

replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd = ana $ \case
    Sub x (Number y) -> AddF [x, Number (-y)]
    other            -> project other

testReplace :: Expr
testReplace = replaceSubWithAdd 
   (Add [Sub (Add [Variable "x", Sub (Variable "y") (Number 34)]) (Number 10), Number 4])

একটি বিকল্প ExprF aকেবল সংজ্ঞায়িত করা এবং তারপরে প্রাপ্ত হতে পারে type Expr = Fix ExprF। এটি উপরের কিছু বয়লারপ্লেট (উদাহরণস্বরূপ দুটি উদাহরণ) সংরক্ষণ করে, Fix (VariableF ...)পরিবর্তে অন্য ব্যবহারকারীর ব্যবহারের ব্যয় Variable ...হিসাবে এবং অন্যান্য নির্মাতাদের জন্য অ্যানালোগুলি।

প্যাটার্ন প্রতিশব্দ ব্যবহার করে কেউ আরও কমাতে পারে (যদিও আরও কিছুটা বয়লারপ্লেটের দামে)।


আপডেট: অবশেষে আমি হ্যাস্কেল টেম্পলেট ব্যবহার করে স্বয়ংক্রিয় সরঞ্জামটি পেয়েছি। এটি পুরো কোডটি যুক্তিসঙ্গতভাবে সংক্ষিপ্ত করে তোলে। নোট করুন যে ExprFফান্টেক্টর এবং উপরে দুটি উদাহরণ হুডের নীচে এখনও বিদ্যমান এবং আমাদের এখনও তাদের ব্যবহার করতে হবে। আমরা কেবল সেগুলি ম্যানুয়ালি সংজ্ঞায়িত করার ঝামেলা সংরক্ষণ করি, তবে এটি একাই প্রচুর প্রচেষ্টা সাশ্রয় করে।

{-# LANGUAGE DeriveFunctor, DeriveTraversable, TypeFamilies, LambdaCase, TemplateHaskell #-}
{-# OPTIONS -Wall #-}
module AnaExpr where

import Data.Functor.Foldable
import Data.Functor.Foldable.TH

data Expr
  = Variable String
  | Number Int
  | Add [Expr]
  | Sub Expr Expr
  deriving (Show)

makeBaseFunctor ''Expr

substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue = ana $ \case
    Variable x | x == name -> NumberF newValue
    other                  -> project other

testSub :: Expr
testSub = substituteName "x" 42 (Add [Add [Variable "x"], Number 0])

replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd = ana $ \case
    Sub x (Number y) -> AddF [x, Number (-y)]
    other            -> project other

testReplace :: Expr
testReplace = replaceSubWithAdd 
   (Add [Sub (Add [Variable "x", Sub (Variable "y") (Number 34)]) (Number 10), Number 4])

আপনার কি সত্যিকারের Exprমতো কিছু না করে স্পষ্ট করে সংজ্ঞা দিতে হবে type Expr = Fix ExprF?
চিপনার

2
@ চেপারার আমি সংক্ষেপে এর বিকল্প হিসাবে উল্লেখ করেছি। প্রতিটি কিছুর জন্য ডাবল কনস্ট্রাক্টর ব্যবহার করতে কিছুটা অসুবিধে হয়: Fix+ আসল কনস্ট্রাক্টর। টিএম অটোমেশনের সাথে শেষ পন্থাটি ব্যবহার করা ভাল, আইএমও।
চি

19

বিকল্প পদ্ধতির হিসাবে এটি uniplateপ্যাকেজের জন্য একটি সাধারণ ব্যবহারের কেস । এটি Data.Dataবয়লারপ্লেট উত্পন্ন করতে টেম্পলেট হাস্কেলের চেয়ে জেনেরিক ব্যবহার করতে পারে , সুতরাং যদি আপনি Dataউদাহরণগুলি পান তবে Expr:

import Data.Data

data Expr
  = Variable String
  | Number Int
  | Add [Expr]
  | Sub Expr Expr
  deriving (Show, Data)

তারপরে transformফাংশনটি Data.Generics.Uniplate.Dataপ্রতিটি নেস্টেডকে পুনরাবৃত্তভাবে একটি ফাংশন প্রয়োগ করে Expr:

import Data.Generics.Uniplate.Data

substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue = transform f
  where f (Variable x) | x == name = Number newValue
        f other = other

replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd = transform f
  where f (Sub x (Number y)) = Add [x, Number (-y)]
        f other = other

দ্রষ্টব্য যে replaceSubWithAddবিশেষত, ফাংশনটি fএকটি পুনরাবৃত্তিযোগ্য প্রতিস্থাপন সম্পাদন করতে লেখা হয়েছিল; transformএটিকে পুনরাবৃত্ত করে তোলে x :: Expr, সুতরাং এটি সহায়ক anaচিঠিতে @ চি এর উত্তরের মতো একই যাদু করছে :

> substituteName "x" 42 (Add [Add [Variable "x"], Number 0])
Add [Add [Number 42],Number 0]
> replaceSubWithAdd (Add [Sub (Add [Variable "x", 
                     Sub (Variable "y") (Number 34)]) (Number 10), Number 4])
Add [Add [Add [Variable "x",Add [Variable "y",Number (-34)]],Number (-10)],Number 4]
> 

এটি @ চি এর টেম্পলেট হাস্কেল সমাধানের চেয়ে কম নয়। একটি সম্ভাব্য সুবিধা হ'ল এটি uniplateকিছু অতিরিক্ত ফাংশন সরবরাহ করে যা সহায়ক হতে পারে। উদাহরণস্বরূপ, আপনি যদি এটির descendজায়গায় ব্যবহার করেন তবে transformএটি কেবলমাত্র তাত্ক্ষণিক শিশুদের রূপান্তর করে যা পুনরাবৃত্তি ঘটে যেখানে আপনাকে নিয়ন্ত্রণ দিতে পারে বা আপনি rewriteএকটি নির্দিষ্ট পয়েন্টে পৌঁছানো পর্যন্ত রূপান্তরগুলির ফলাফলটিকে পুনরায় রূপান্তর করতে ব্যবহার করতে পারেন । একটি সম্ভাব্য অসুবিধা হ'ল "আনমোরফিজম" "ইউনিপ্লেট" এর চেয়ে শীতল শোনায়।

সম্পূর্ণ প্রোগ্রাম:

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Data                     -- in base
import Data.Generics.Uniplate.Data   -- package uniplate

data Expr
  = Variable String
  | Number Int
  | Add [Expr]
  | Sub Expr Expr
  deriving (Show, Data)

substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue = transform f
  where f (Variable x) | x == name = Number newValue
        f other = other

replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd = transform f
  where f (Sub x (Number y)) = Add [x, Number (-y)]
        f other = other

replaceSubWithAdd1 :: Expr -> Expr
replaceSubWithAdd1 = descend f
  where f (Sub x (Number y)) = Add [x, Number (-y)]
        f other = other

main = do
  print $ substituteName "x" 42 (Add [Add [Variable "x"], Number 0])
  print $ replaceSubWithAdd e
  print $ replaceSubWithAdd1 e
  where e = Add [Sub (Add [Variable "x", Sub (Variable "y") (Number 34)])
                     (Number 10), Number 4]
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.